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