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)) { 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 : getContainedResources().getContainedResources()) { 363 IIdType resourceId = getContainedResources().getResourceId(next); 364 theEventWriter.writeStartElement("contained"); 365 String value = resourceId.getValue(); 366 encodeResourceToXmlStreamWriter( 367 next, theEventWriter, true, fixContainedResourceId(value), theEncodeContext); 368 theEventWriter.writeEndElement(); 369 } 370 break; 371 } 372 case RESOURCE: { 373 IBaseResource resource = (IBaseResource) theElement; 374 String resourceName = getContext().getResourceType(resource); 375 if (!super.shouldEncodeResource(resourceName, theEncodeContext)) { 376 break; 377 } 378 theEventWriter.writeStartElement(theChildName); 379 theEncodeContext.pushPath(resourceName, true); 380 encodeResourceToXmlStreamWriter(resource, theEventWriter, theIncludedResource, theEncodeContext); 381 theEncodeContext.popPath(); 382 theEventWriter.writeEndElement(); 383 break; 384 } 385 case PRIMITIVE_XHTML: { 386 XhtmlDt dt = XhtmlDt.class.cast(theElement); 387 if (dt.hasContent()) { 388 encodeXhtml(dt, theEventWriter); 389 } 390 break; 391 } 392 case PRIMITIVE_XHTML_HL7ORG: { 393 IBaseXhtml dt = IBaseXhtml.class.cast(theElement); 394 if (!dt.isEmpty()) { 395 // TODO: this is probably not as efficient as it could be 396 XhtmlDt hdt = new XhtmlDt(); 397 hdt.setValueAsString(dt.getValueAsString()); 398 encodeXhtml(hdt, theEventWriter); 399 } 400 break; 401 } 402 case EXTENSION_DECLARED: 403 case UNDECL_EXT: { 404 throw new IllegalStateException(Msg.code(1853) + "state should not happen: " + childDef.getName()); 405 } 406 } 407 408 writeCommentsPost(theEventWriter, theElement); 409 410 } finally { 411 theEncodeContext.popPath(); 412 } 413 } 414 415 private void encodeCompositeElementToStreamWriter( 416 IBaseResource theResource, 417 IBase theElement, 418 XMLStreamWriter theEventWriter, 419 boolean theContainedResource, 420 CompositeChildElement theParent, 421 EncodeContext theEncodeContext) 422 throws XMLStreamException, DataFormatException { 423 424 for (CompositeChildElement nextChildElem : 425 super.compositeChildIterator(theElement, theContainedResource, theParent, theEncodeContext)) { 426 427 BaseRuntimeChildDefinition nextChild = nextChildElem.getDef(); 428 429 if (nextChild.getElementName().equals("url") && theElement instanceof IBaseExtension) { 430 /* 431 * XML encoding is a one-off for extensions. The URL element goes in an attribute 432 * instead of being encoded as a normal element, only for XML encoding 433 */ 434 continue; 435 } 436 437 if (nextChild instanceof RuntimeChildNarrativeDefinition) { 438 Optional<IBase> narr = nextChild.getAccessor().getFirstValueOrNull(theElement); 439 INarrativeGenerator gen = getContext().getNarrativeGenerator(); 440 if (gen != null && narr.isPresent() == false) { 441 gen.populateResourceNarrative(getContext(), theResource); 442 } 443 444 narr = nextChild.getAccessor().getFirstValueOrNull(theElement); 445 if (narr.isPresent()) { 446 RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild; 447 String childName = nextChild.getChildNameByDatatype(child.getDatatype()); 448 BaseRuntimeElementDefinition<?> type = child.getChildByName(childName); 449 encodeChildElementToStreamWriter( 450 theResource, 451 theEventWriter, 452 nextChild, 453 narr.get(), 454 childName, 455 type, 456 null, 457 theContainedResource, 458 nextChildElem, 459 theEncodeContext); 460 continue; 461 } 462 } 463 464 if (nextChild instanceof RuntimeChildContainedResources) { 465 encodeChildElementToStreamWriter( 466 theResource, 467 theEventWriter, 468 nextChild, 469 null, 470 nextChild.getChildNameByDatatype(null), 471 nextChild.getChildElementDefinitionByDatatype(null), 472 null, 473 theContainedResource, 474 nextChildElem, 475 theEncodeContext); 476 } else { 477 478 List<? extends IBase> values = nextChild.getAccessor().getValues(theElement); 479 values = preProcessValues(nextChild, theResource, values, nextChildElem, theEncodeContext); 480 481 if (values == null || values.isEmpty()) { 482 continue; 483 } 484 for (IBase nextValue : values) { 485 if ((nextValue == null || nextValue.isEmpty())) { 486 continue; 487 } 488 489 BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue); 490 if (childNameAndDef == null) { 491 continue; 492 } 493 494 String childName = childNameAndDef.getChildName(); 495 BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef(); 496 String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl()); 497 498 boolean isExtension = childName.equals("extension") || childName.equals("modifierExtension"); 499 if (isExtension && nextValue instanceof IBaseExtension) { 500 IBaseExtension<?, ?> ext = (IBaseExtension<?, ?>) nextValue; 501 if (isBlank(ext.getUrl())) { 502 ParseLocation loc = new ParseLocation(theEncodeContext.toString() + "." + childName); 503 getErrorHandler().missingRequiredElement(loc, "url"); 504 } 505 if (ext.getValue() != null && ext.getExtension().size() > 0) { 506 ParseLocation loc = new ParseLocation(theEncodeContext.toString() + "." + childName); 507 getErrorHandler().extensionContainsValueAndNestedExtensions(loc); 508 } 509 } 510 511 if (extensionUrl != null && isExtension == false) { 512 encodeExtension( 513 theResource, 514 theEventWriter, 515 theContainedResource, 516 nextChildElem, 517 nextChild, 518 nextValue, 519 childName, 520 extensionUrl, 521 childDef, 522 theEncodeContext); 523 } else if (nextChild instanceof RuntimeChildExtension) { 524 IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue; 525 if ((extension.getValue() == null 526 || extension.getValue().isEmpty())) { 527 if (extension.getExtension().isEmpty()) { 528 continue; 529 } 530 } 531 encodeChildElementToStreamWriter( 532 theResource, 533 theEventWriter, 534 nextChild, 535 nextValue, 536 childName, 537 childDef, 538 getExtensionUrl(extension.getUrl()), 539 theContainedResource, 540 nextChildElem, 541 theEncodeContext); 542 } else { 543 encodeChildElementToStreamWriter( 544 theResource, 545 theEventWriter, 546 nextChild, 547 nextValue, 548 childName, 549 childDef, 550 extensionUrl, 551 theContainedResource, 552 nextChildElem, 553 theEncodeContext); 554 } 555 } 556 } 557 } 558 } 559 560 private void encodeExtension( 561 IBaseResource theResource, 562 XMLStreamWriter theEventWriter, 563 boolean theContainedResource, 564 CompositeChildElement nextChildElem, 565 BaseRuntimeChildDefinition nextChild, 566 IBase nextValue, 567 String childName, 568 String extensionUrl, 569 BaseRuntimeElementDefinition<?> childDef, 570 EncodeContext theEncodeContext) 571 throws XMLStreamException { 572 BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild; 573 if (extDef.isModifier()) { 574 theEventWriter.writeStartElement("modifierExtension"); 575 } else { 576 theEventWriter.writeStartElement("extension"); 577 } 578 579 String elementId = getCompositeElementId(nextValue); 580 if (isNotBlank(elementId)) { 581 theEventWriter.writeAttribute("id", elementId); 582 } 583 584 if (isBlank(extensionUrl)) { 585 ParseLocation loc = new ParseLocation(theEncodeContext.toString()); 586 getErrorHandler().missingRequiredElement(loc, "url"); 587 } else { 588 theEventWriter.writeAttribute("url", extensionUrl); 589 } 590 591 encodeChildElementToStreamWriter( 592 theResource, 593 theEventWriter, 594 nextChild, 595 nextValue, 596 childName, 597 childDef, 598 null, 599 theContainedResource, 600 nextChildElem, 601 theEncodeContext); 602 theEventWriter.writeEndElement(); 603 } 604 605 private void encodeExtensionsIfPresent( 606 IBaseResource theResource, 607 XMLStreamWriter theWriter, 608 IBase theElement, 609 boolean theIncludedResource, 610 EncodeContext theEncodeContext) 611 throws XMLStreamException, DataFormatException { 612 if (theElement instanceof ISupportsUndeclaredExtensions) { 613 ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement; 614 encodeUndeclaredExtensions( 615 theResource, 616 theWriter, 617 toBaseExtensionList(res.getUndeclaredExtensions()), 618 "extension", 619 theIncludedResource, 620 theEncodeContext); 621 encodeUndeclaredExtensions( 622 theResource, 623 theWriter, 624 toBaseExtensionList(res.getUndeclaredModifierExtensions()), 625 "modifierExtension", 626 theIncludedResource, 627 theEncodeContext); 628 } 629 if (theElement instanceof IBaseHasExtensions) { 630 IBaseHasExtensions res = (IBaseHasExtensions) theElement; 631 encodeUndeclaredExtensions( 632 theResource, theWriter, res.getExtension(), "extension", theIncludedResource, theEncodeContext); 633 } 634 if (theElement instanceof IBaseHasModifierExtensions) { 635 IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement; 636 encodeUndeclaredExtensions( 637 theResource, 638 theWriter, 639 res.getModifierExtension(), 640 "modifierExtension", 641 theIncludedResource, 642 theEncodeContext); 643 } 644 } 645 646 private void encodeResourceToXmlStreamWriter( 647 IBaseResource theResource, 648 XMLStreamWriter theEventWriter, 649 boolean theIncludedResource, 650 EncodeContext theEncodeContext) 651 throws XMLStreamException, DataFormatException { 652 IIdType resourceId = null; 653 654 if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) { 655 resourceId = theResource.getIdElement(); 656 if (theResource.getIdElement().getValue().startsWith("urn:")) { 657 resourceId = null; 658 } 659 } 660 661 if (!theIncludedResource) { 662 if (super.shouldEncodeResourceId(theResource, theEncodeContext) == false) { 663 resourceId = null; 664 } else if (theEncodeContext.getResourcePath().size() == 1 && getEncodeForceResourceId() != null) { 665 resourceId = getEncodeForceResourceId(); 666 } 667 } 668 669 encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId, theEncodeContext); 670 } 671 672 private void encodeResourceToXmlStreamWriter( 673 IBaseResource theResource, 674 XMLStreamWriter theEventWriter, 675 boolean theContainedResource, 676 IIdType theResourceId, 677 EncodeContext theEncodeContext) 678 throws XMLStreamException { 679 RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource); 680 if (resDef == null) { 681 throw new ConfigurationException(Msg.code(1854) + "Unknown resource type: " + theResource.getClass()); 682 } 683 684 if (!theContainedResource) { 685 containResourcesInReferences(theResource); 686 } 687 688 theEventWriter.writeStartElement(resDef.getName()); 689 theEventWriter.writeDefaultNamespace(FHIR_NS); 690 691 if (theResource instanceof IAnyResource) { 692 // HL7.org Structures 693 if (theResourceId != null) { 694 writeCommentsPre(theEventWriter, theResourceId); 695 theEventWriter.writeStartElement("id"); 696 theEventWriter.writeAttribute("value", theResourceId.getIdPart()); 697 encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext); 698 theEventWriter.writeEndElement(); 699 writeCommentsPost(theEventWriter, theResourceId); 700 } 701 702 encodeCompositeElementToStreamWriter( 703 theResource, 704 theResource, 705 theEventWriter, 706 theContainedResource, 707 new CompositeChildElement(resDef, theEncodeContext), 708 theEncodeContext); 709 710 } else { 711 712 // DSTU2+ 713 714 IResource resource = (IResource) theResource; 715 if (theResourceId != null) { 716 /* writeCommentsPre(theEventWriter, theResourceId); 717 writeOptionalTagWithValue(theEventWriter, "id", theResourceId.getIdPart()); 718 writeCommentsPost(theEventWriter, theResourceId);*/ 719 theEventWriter.writeStartElement("id"); 720 theEventWriter.writeAttribute("value", theResourceId.getIdPart()); 721 encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext); 722 theEventWriter.writeEndElement(); 723 writeCommentsPost(theEventWriter, theResourceId); 724 } 725 726 InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); 727 IdDt resourceId = resource.getId(); 728 String versionIdPart = resourceId.getVersionIdPart(); 729 if (isBlank(versionIdPart)) { 730 versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource); 731 } 732 List<BaseCodingDt> securityLabels = 733 extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS); 734 List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES); 735 profiles = super.getProfileTagsForEncoding(resource, profiles); 736 737 TagList tags = getMetaTagsForEncoding((resource), theEncodeContext); 738 739 if (super.shouldEncodeResourceMeta(resource, theEncodeContext) 740 && ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) { 741 theEventWriter.writeStartElement("meta"); 742 if (shouldEncodePath(resource, "meta.versionId", theEncodeContext)) { 743 writeOptionalTagWithValue(theEventWriter, "versionId", versionIdPart); 744 } 745 if (updated != null) { 746 if (shouldEncodePath(resource, "meta.lastUpdated", theEncodeContext)) { 747 writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString()); 748 } 749 } 750 751 for (IIdType profile : profiles) { 752 theEventWriter.writeStartElement("profile"); 753 theEventWriter.writeAttribute("value", profile.getValue()); 754 theEventWriter.writeEndElement(); 755 } 756 for (BaseCodingDt securityLabel : securityLabels) { 757 theEventWriter.writeStartElement("security"); 758 encodeCompositeElementToStreamWriter( 759 resource, securityLabel, theEventWriter, theContainedResource, null, theEncodeContext); 760 theEventWriter.writeEndElement(); 761 } 762 if (tags != null) { 763 for (Tag tag : tags) { 764 if (tag.isEmpty()) { 765 continue; 766 } 767 theEventWriter.writeStartElement("tag"); 768 writeOptionalTagWithValue(theEventWriter, "system", tag.getScheme()); 769 writeOptionalTagWithValue(theEventWriter, "code", tag.getTerm()); 770 writeOptionalTagWithValue(theEventWriter, "display", tag.getLabel()); 771 writeOptionalTagWithValue(theEventWriter, "version", tag.getVersion()); 772 Boolean userSelected = tag.getUserSelectedBoolean(); 773 if (userSelected != null) { 774 writeOptionalTagWithValue(theEventWriter, "userSelected", userSelected.toString()); 775 } 776 theEventWriter.writeEndElement(); 777 } 778 } 779 theEventWriter.writeEndElement(); 780 } 781 782 if (theResource instanceof IBaseBinary) { 783 IBaseBinary bin = (IBaseBinary) theResource; 784 writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType()); 785 writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64()); 786 } else { 787 encodeCompositeElementToStreamWriter( 788 theResource, 789 theResource, 790 theEventWriter, 791 theContainedResource, 792 new CompositeChildElement(resDef, theEncodeContext), 793 theEncodeContext); 794 } 795 } 796 797 theEventWriter.writeEndElement(); 798 } 799 800 private void encodeUndeclaredExtensions( 801 IBaseResource theResource, 802 XMLStreamWriter theEventWriter, 803 List<? extends IBaseExtension<?, ?>> theExtensions, 804 String tagName, 805 boolean theIncludedResource, 806 EncodeContext theEncodeContext) 807 throws XMLStreamException, DataFormatException { 808 for (IBaseExtension<?, ?> next : theExtensions) { 809 if (next == null 810 || (ElementUtil.isEmpty(next.getValue()) 811 && next.getExtension().isEmpty())) { 812 continue; 813 } 814 815 writeCommentsPre(theEventWriter, next); 816 817 theEventWriter.writeStartElement(tagName); 818 819 String elementId = getCompositeElementId(next); 820 if (isNotBlank(elementId)) { 821 theEventWriter.writeAttribute("id", elementId); 822 } 823 824 String url = getExtensionUrl(next.getUrl()); 825 if (isNotBlank(url)) { 826 theEventWriter.writeAttribute("url", url); 827 } 828 829 if (next.getValue() != null) { 830 IBaseDatatype value = next.getValue(); 831 RuntimeChildUndeclaredExtensionDefinition extDef = 832 getContext().getRuntimeChildUndeclaredExtensionDefinition(); 833 String childName = extDef.getChildNameByDatatype(value.getClass()); 834 BaseRuntimeElementDefinition<?> childDef; 835 if (childName == null) { 836 childDef = getContext().getElementDefinition(value.getClass()); 837 if (childDef == null) { 838 throw new ConfigurationException( 839 Msg.code(1855) + "Unable to encode extension, unrecognized child element type: " 840 + value.getClass().getCanonicalName()); 841 } 842 childName = RuntimeChildUndeclaredExtensionDefinition.createExtensionChildName(childDef); 843 } else { 844 childDef = extDef.getChildElementDefinitionByDatatype(value.getClass()); 845 if (childDef == null) { 846 throw new ConfigurationException( 847 Msg.code(1856) + "Unable to encode extension, unrecognized child element type: " 848 + value.getClass().getCanonicalName()); 849 } 850 } 851 encodeChildElementToStreamWriter( 852 theResource, 853 theEventWriter, 854 extDef, 855 value, 856 childName, 857 childDef, 858 null, 859 theIncludedResource, 860 null, 861 theEncodeContext); 862 } 863 864 // child extensions 865 encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource, theEncodeContext); 866 867 theEventWriter.writeEndElement(); 868 869 writeCommentsPost(theEventWriter, next); 870 } 871 } 872 873 private void encodeXhtml(XhtmlDt theDt, XMLStreamWriter theEventWriter) throws XMLStreamException { 874 if (theDt == null || theDt.getValue() == null) { 875 return; 876 } 877 878 List<XMLEvent> events = XmlUtil.parse(theDt.getValue()); 879 boolean firstElement = true; 880 881 for (XMLEvent event : events) { 882 switch (event.getEventType()) { 883 case XMLStreamConstants.ATTRIBUTE: 884 Attribute attr = (Attribute) event; 885 if (isBlank(attr.getName().getPrefix())) { 886 if (isBlank(attr.getName().getNamespaceURI())) { 887 theEventWriter.writeAttribute(attr.getName().getLocalPart(), attr.getValue()); 888 } else { 889 theEventWriter.writeAttribute( 890 attr.getName().getNamespaceURI(), 891 attr.getName().getLocalPart(), 892 attr.getValue()); 893 } 894 } else { 895 theEventWriter.writeAttribute( 896 attr.getName().getPrefix(), 897 attr.getName().getNamespaceURI(), 898 attr.getName().getLocalPart(), 899 attr.getValue()); 900 } 901 902 break; 903 case XMLStreamConstants.CDATA: 904 theEventWriter.writeCData(((Characters) event).getData()); 905 break; 906 case XMLStreamConstants.CHARACTERS: 907 case XMLStreamConstants.SPACE: 908 String data = ((Characters) event).getData(); 909 theEventWriter.writeCharacters(data); 910 break; 911 case XMLStreamConstants.COMMENT: 912 theEventWriter.writeComment(((Comment) event).getText()); 913 break; 914 case XMLStreamConstants.END_ELEMENT: 915 theEventWriter.writeEndElement(); 916 break; 917 case XMLStreamConstants.ENTITY_REFERENCE: 918 EntityReference er = (EntityReference) event; 919 theEventWriter.writeEntityRef(er.getName()); 920 break; 921 case XMLStreamConstants.NAMESPACE: 922 Namespace ns = (Namespace) event; 923 theEventWriter.writeNamespace(ns.getPrefix(), ns.getNamespaceURI()); 924 break; 925 case XMLStreamConstants.START_ELEMENT: 926 StartElement se = event.asStartElement(); 927 if (firstElement) { 928 if (StringUtils.isBlank(se.getName().getPrefix())) { 929 String namespaceURI = se.getName().getNamespaceURI(); 930 if (StringUtils.isBlank(namespaceURI)) { 931 namespaceURI = "http://www.w3.org/1999/xhtml"; 932 } 933 theEventWriter.writeStartElement(se.getName().getLocalPart()); 934 theEventWriter.writeDefaultNamespace(namespaceURI); 935 } else { 936 String prefix = se.getName().getPrefix(); 937 String namespaceURI = se.getName().getNamespaceURI(); 938 theEventWriter.writeStartElement( 939 prefix, se.getName().getLocalPart(), namespaceURI); 940 theEventWriter.writeNamespace(prefix, namespaceURI); 941 } 942 firstElement = false; 943 } else { 944 if (isBlank(se.getName().getPrefix())) { 945 if (isBlank(se.getName().getNamespaceURI())) { 946 theEventWriter.writeStartElement(se.getName().getLocalPart()); 947 } else { 948 if (StringUtils.isBlank(se.getName().getPrefix())) { 949 theEventWriter.writeStartElement( 950 se.getName().getLocalPart()); 951 // theEventWriter.writeDefaultNamespace(se.getName().getNamespaceURI()); 952 } else { 953 theEventWriter.writeStartElement( 954 se.getName().getNamespaceURI(), 955 se.getName().getLocalPart()); 956 } 957 } 958 } else { 959 theEventWriter.writeStartElement( 960 se.getName().getPrefix(), 961 se.getName().getLocalPart(), 962 se.getName().getNamespaceURI()); 963 } 964 } 965 for (Iterator<?> attrIter = se.getAttributes(); attrIter.hasNext(); ) { 966 Attribute next = (Attribute) attrIter.next(); 967 if (isBlank(next.getName().getNamespaceURI())) { 968 theEventWriter.writeAttribute(next.getName().getLocalPart(), next.getValue()); 969 } else { 970 theEventWriter.writeAttribute( 971 next.getName().getPrefix(), 972 next.getName().getNamespaceURI(), 973 next.getName().getLocalPart(), 974 next.getValue()); 975 } 976 } 977 break; 978 case XMLStreamConstants.DTD: 979 case XMLStreamConstants.END_DOCUMENT: 980 case XMLStreamConstants.ENTITY_DECLARATION: 981 case XMLStreamConstants.NOTATION_DECLARATION: 982 case XMLStreamConstants.PROCESSING_INSTRUCTION: 983 case XMLStreamConstants.START_DOCUMENT: 984 break; 985 } 986 } 987 } 988 989 @Override 990 public EncodingEnum getEncoding() { 991 return EncodingEnum.XML; 992 } 993 994 private <T extends IBaseResource> T parseResource(Class<T> theResourceType, XMLEventReader theStreamReader) { 995 ParserState<T> parserState = 996 ParserState.getPreResourceInstance(this, theResourceType, getContext(), false, getErrorHandler()); 997 return doXmlLoop(theStreamReader, parserState); 998 } 999 1000 @Override 1001 public IParser setPrettyPrint(boolean thePrettyPrint) { 1002 myPrettyPrint = thePrettyPrint; 1003 return this; 1004 } 1005 1006 /** 1007 * This is just to work around the fact that casting java.util.List<ca.uhn.fhir.model.api.ExtensionDt> to 1008 * java.util.List<? extends org.hl7.fhir.instance.model.api.IBaseExtension<?, ?>> seems to be 1009 * rejected by the compiler some of the time. 1010 */ 1011 private <Q extends IBaseExtension<?, ?>> List<IBaseExtension<?, ?>> toBaseExtensionList(final List<Q> theList) { 1012 List<IBaseExtension<?, ?>> retVal = new ArrayList<IBaseExtension<?, ?>>(theList.size()); 1013 retVal.addAll(theList); 1014 return retVal; 1015 } 1016 1017 private void writeCommentsPost(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException { 1018 if (theElement != null && theElement.hasFormatComment()) { 1019 for (String next : theElement.getFormatCommentsPost()) { 1020 if (isNotBlank(next)) { 1021 theEventWriter.writeComment(next); 1022 } 1023 } 1024 } 1025 } 1026 1027 private void writeCommentsPre(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException { 1028 if (theElement != null && theElement.hasFormatComment()) { 1029 for (String next : theElement.getFormatCommentsPre()) { 1030 if (isNotBlank(next)) { 1031 theEventWriter.writeComment(next); 1032 } 1033 } 1034 } 1035 } 1036 1037 private void writeOptionalTagWithValue(XMLStreamWriter theEventWriter, String theName, String theValue) 1038 throws XMLStreamException { 1039 if (StringUtils.isNotBlank(theValue)) { 1040 theEventWriter.writeStartElement(theName); 1041 theEventWriter.writeAttribute("value", theValue); 1042 theEventWriter.writeEndElement(); 1043 } 1044 } 1045}