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.RuntimeChildDirectResource; 029import ca.uhn.fhir.context.RuntimeChildExtension; 030import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition; 031import ca.uhn.fhir.context.RuntimeResourceDefinition; 032import ca.uhn.fhir.i18n.Msg; 033import ca.uhn.fhir.model.api.IResource; 034import ca.uhn.fhir.narrative.INarrativeGenerator; 035import ca.uhn.fhir.rest.api.EncodingEnum; 036import ca.uhn.fhir.util.rdf.RDFUtil; 037import org.apache.commons.lang3.StringUtils; 038import org.apache.jena.datatypes.xsd.XSDDatatype; 039import org.apache.jena.irix.IRIs; 040import org.apache.jena.rdf.model.Literal; 041import org.apache.jena.rdf.model.Model; 042import org.apache.jena.rdf.model.RDFNode; 043import org.apache.jena.rdf.model.Resource; 044import org.apache.jena.rdf.model.Statement; 045import org.apache.jena.rdf.model.StmtIterator; 046import org.apache.jena.riot.Lang; 047import org.apache.jena.vocabulary.RDF; 048import org.hl7.fhir.instance.model.api.IAnyResource; 049import org.hl7.fhir.instance.model.api.IBase; 050import org.hl7.fhir.instance.model.api.IBaseBackboneElement; 051import org.hl7.fhir.instance.model.api.IBaseDatatypeElement; 052import org.hl7.fhir.instance.model.api.IBaseElement; 053import org.hl7.fhir.instance.model.api.IBaseExtension; 054import org.hl7.fhir.instance.model.api.IBaseHasExtensions; 055import org.hl7.fhir.instance.model.api.IBaseResource; 056import org.hl7.fhir.instance.model.api.IBaseXhtml; 057import org.hl7.fhir.instance.model.api.IDomainResource; 058import org.hl7.fhir.instance.model.api.IIdType; 059import org.hl7.fhir.instance.model.api.INarrative; 060import org.hl7.fhir.instance.model.api.IPrimitiveType; 061 062import java.io.Reader; 063import java.io.Writer; 064import java.util.Arrays; 065import java.util.Comparator; 066import java.util.HashMap; 067import java.util.List; 068import java.util.Map; 069 070import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.ID_DATATYPE; 071import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.PRIMITIVE_DATATYPE; 072import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.PRIMITIVE_XHTML; 073import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.PRIMITIVE_XHTML_HL7ORG; 074 075/** 076 * This class is the FHIR RDF parser/encoder. Users should not interact with this class directly, but should use 077 * {@link FhirContext#newRDFParser()} to get an instance. 078 */ 079public class RDFParser extends BaseParser { 080 081 private static final String VALUE = "value"; 082 private static final String FHIR_INDEX = "index"; 083 private static final String FHIR_PREFIX = "fhir"; 084 private static final String FHIR_NS = "http://hl7.org/fhir/"; 085 private static final String RDF_PREFIX = "rdf"; 086 private static final String RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; 087 private static final String RDFS_PREFIX = "rdfs"; 088 private static final String RDFS_NS = "http://www.w3.org/2000/01/rdf-schema#"; 089 private static final String XSD_PREFIX = "xsd"; 090 private static final String XSD_NS = "http://www.w3.org/2001/XMLSchema#"; 091 private static final String SCT_PREFIX = "sct"; 092 private static final String SCT_NS = "http://snomed.info/id#"; 093 private static final String EXTENSION_URL = "Extension.url"; 094 private static final String ELEMENT_EXTENSION = "Element.extension"; 095 096 private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(RDFParser.class); 097 098 public static final String NODE_ROLE = "nodeRole"; 099 private static final List<String> ignoredPredicates = 100 Arrays.asList(RDF.type.getURI(), FHIR_NS + FHIR_INDEX, FHIR_NS + NODE_ROLE); 101 public static final String TREE_ROOT = "treeRoot"; 102 public static final String RESOURCE_ID = "Resource.id"; 103 public static final String ID = "id"; 104 public static final String ELEMENT_ID = "Element.id"; 105 public static final String DOMAIN_RESOURCE_CONTAINED = "DomainResource.contained"; 106 public static final String EXTENSION = "extension"; 107 public static final String CONTAINED = "contained"; 108 public static final String MODIFIER_EXTENSION = "modifierExtension"; 109 private final Map<Class, String> classToFhirTypeMap = new HashMap<>(); 110 111 private final Lang lang; 112 113 /** 114 * Do not use this constructor, the recommended way to obtain a new instance of the RDF parser is to invoke 115 * {@link FhirContext#newRDFParser()}. 116 * 117 * @param parserErrorHandler the Parser Error Handler 118 */ 119 public RDFParser(final FhirContext context, final IParserErrorHandler parserErrorHandler, final Lang lang) { 120 super(context, parserErrorHandler); 121 this.lang = lang; 122 } 123 124 @Override 125 public EncodingEnum getEncoding() { 126 return EncodingEnum.RDF; 127 } 128 129 @Override 130 public IParser setPrettyPrint(final boolean prettyPrint) { 131 return this; 132 } 133 134 /** 135 * Writes the provided resource to the writer. This should only be called for the top-level resource being encoded. 136 * @param resource FHIR resource for writing 137 * @param writer The writer to write to -- Note: Jena prefers streams over writers 138 * @param encodeContext encoding content from parent 139 */ 140 @Override 141 protected void doEncodeResourceToWriter( 142 final IBaseResource resource, final Writer writer, final EncodeContext encodeContext) { 143 Model rdfModel = RDFUtil.initializeRDFModel(); 144 145 // Establish the namespaces and prefixes needed 146 HashMap<String, String> prefixes = new HashMap<>(); 147 prefixes.put(RDF_PREFIX, RDF_NS); 148 prefixes.put(RDFS_PREFIX, RDFS_NS); 149 prefixes.put(XSD_PREFIX, XSD_NS); 150 prefixes.put(FHIR_PREFIX, FHIR_NS); 151 prefixes.put(SCT_PREFIX, SCT_NS); 152 153 for (String key : prefixes.keySet()) { 154 rdfModel.setNsPrefix(key, prefixes.get(key)); 155 } 156 157 IIdType resourceId = processResourceID(resource, encodeContext); 158 159 encodeResourceToRDFStreamWriter(resource, rdfModel, false, resourceId, encodeContext, true, null); 160 161 RDFUtil.writeRDFModel(writer, rdfModel, lang); 162 } 163 164 /** 165 * Parses RDF content to a FHIR resource using Apache Jena 166 * @param resourceType Class of FHIR resource being deserialized 167 * @param reader Reader containing RDF (turtle) content 168 * @param <T> Type parameter denoting which resource is being parsed 169 * @return Populated FHIR resource 170 * @throws DataFormatException Exception that can be thrown from parser 171 */ 172 @Override 173 protected <T extends IBaseResource> T doParseResource(final Class<T> resourceType, final Reader reader) 174 throws DataFormatException { 175 Model model = RDFUtil.readRDFToModel(reader, this.lang); 176 return parseResource(resourceType, model); 177 } 178 179 private Resource encodeResourceToRDFStreamWriter( 180 final IBaseResource resource, 181 final Model rdfModel, 182 final boolean containedResource, 183 final IIdType resourceId, 184 final EncodeContext encodeContext, 185 final boolean rootResource, 186 Resource parentResource) { 187 188 RuntimeResourceDefinition resDef = getContext().getResourceDefinition(resource); 189 if (resDef == null) { 190 throw new ConfigurationException(Msg.code(1845) + "Unknown resource type: " + resource.getClass()); 191 } 192 193 if (!containedResource) { 194 containResourcesInReferences(resource); 195 } 196 197 if (!(resource instanceof IAnyResource)) { 198 throw new IllegalStateException(Msg.code(1846) + "Unsupported resource found: " 199 + resource.getClass().getName()); 200 } 201 202 // Create absolute IRI for the resource 203 String uriBase = resource.getIdElement().getBaseUrl(); 204 if (uriBase == null) { 205 uriBase = getServerBaseUrl(); 206 } 207 if (uriBase == null) { 208 uriBase = FHIR_NS; 209 } 210 if (!uriBase.endsWith("/")) { 211 uriBase = uriBase + "/"; 212 } 213 214 if (parentResource == null) { 215 if (!resource.getIdElement().toUnqualified().hasIdPart()) { 216 parentResource = rdfModel.getResource(null); 217 } else { 218 219 String resourceUri = IRIs.resolve( 220 uriBase, resource.getIdElement().toUnqualified().toString()) 221 .toString(); 222 parentResource = rdfModel.getResource(resourceUri); 223 } 224 // If the resource already exists and has statements, return that existing resource. 225 if (parentResource != null 226 && parentResource.listProperties().toList().size() > 0) { 227 return parentResource; 228 } else if (parentResource == null) { 229 return null; 230 } 231 } 232 233 parentResource.addProperty(RDF.type, rdfModel.createProperty(FHIR_NS + resDef.getName())); 234 235 // Only the top-level resource should have the nodeRole set to treeRoot 236 if (rootResource) { 237 parentResource.addProperty( 238 rdfModel.createProperty(FHIR_NS + NODE_ROLE), rdfModel.createProperty(FHIR_NS + TREE_ROOT)); 239 } 240 241 if (resourceId != null 242 && resourceId.getIdPart() != null 243 && !resourceId.getValue().startsWith("urn:")) { 244 parentResource.addProperty( 245 rdfModel.createProperty(FHIR_NS + RESOURCE_ID), 246 createFhirValueBlankNode(rdfModel, resourceId.getIdPart())); 247 } 248 249 encodeCompositeElementToStreamWriter( 250 resource, 251 resource, 252 rdfModel, 253 parentResource, 254 containedResource, 255 new CompositeChildElement(resDef, encodeContext), 256 encodeContext); 257 258 return parentResource; 259 } 260 261 /** 262 * Utility method to create a blank node with a fhir:value predicate 263 * @param rdfModel Model to create node within 264 * @param value value object - assumed to be xsd:string 265 * @return Blank node resource containing fhir:value 266 */ 267 private Resource createFhirValueBlankNode(Model rdfModel, String value) { 268 return createFhirValueBlankNode(rdfModel, value, XSDDatatype.XSDstring, null); 269 } 270 /** 271 * Utility method to create a blank node with a fhir:value predicate accepting a specific data type and index 272 * @param rdfModel Model to create node within 273 * @param value value object 274 * @param xsdDataType data type for value 275 * @param cardinalityIndex if a collection, this value is written as a fhir:index predicate 276 * @return Blank node resource containing fhir:value (and possibly fhir:index) 277 */ 278 private Resource createFhirValueBlankNode( 279 Model rdfModel, String value, XSDDatatype xsdDataType, Integer cardinalityIndex) { 280 Resource fhirValueBlankNodeResource = rdfModel.createResource() 281 .addProperty(rdfModel.createProperty(FHIR_NS + VALUE), rdfModel.createTypedLiteral(value, xsdDataType)); 282 283 if (cardinalityIndex != null && cardinalityIndex > -1) { 284 fhirValueBlankNodeResource.addProperty( 285 rdfModel.createProperty(FHIR_NS + FHIR_INDEX), 286 rdfModel.createTypedLiteral(cardinalityIndex, XSDDatatype.XSDinteger)); 287 } 288 return fhirValueBlankNodeResource; 289 } 290 291 /** 292 * Builds the predicate name based on field definition 293 * @param resource Resource being interrogated 294 * @param definition field definition 295 * @param childName childName which been massaged for different data types 296 * @return String of predicate name 297 */ 298 private String constructPredicateName( 299 IBaseResource resource, BaseRuntimeChildDefinition definition, String childName, IBase parentElement) { 300 String basePropertyName = FHIR_NS + resource.fhirType() + "." + childName; 301 String classBasedPropertyName; 302 303 if (definition instanceof BaseRuntimeDeclaredChildDefinition) { 304 BaseRuntimeDeclaredChildDefinition declaredDef = (BaseRuntimeDeclaredChildDefinition) definition; 305 Class declaringClass = declaredDef.getField().getDeclaringClass(); 306 if (declaringClass != resource.getClass()) { 307 String property = null; 308 if (IBaseBackboneElement.class.isAssignableFrom(declaringClass) 309 || IBaseDatatypeElement.class.isAssignableFrom(declaringClass)) { 310 if (classToFhirTypeMap.containsKey(declaringClass)) { 311 property = classToFhirTypeMap.get(declaringClass); 312 } else { 313 try { 314 IBase elem = (IBase) 315 declaringClass.getDeclaredConstructor().newInstance(); 316 property = elem.fhirType(); 317 classToFhirTypeMap.put(declaringClass, property); 318 } catch (Exception ex) { 319 logger.debug("Error instantiating an " + declaringClass.getSimpleName() 320 + " to retrieve its FhirType"); 321 } 322 } 323 } else { 324 if ("MetadataResource".equals(declaringClass.getSimpleName())) { 325 property = resource.getClass().getSimpleName(); 326 } else { 327 property = declaredDef.getField().getDeclaringClass().getSimpleName(); 328 } 329 } 330 classBasedPropertyName = FHIR_NS + property + "." + childName; 331 return classBasedPropertyName; 332 } 333 } 334 return basePropertyName; 335 } 336 337 private Model encodeChildElementToStreamWriter( 338 final IBaseResource resource, 339 IBase parentElement, 340 Model rdfModel, 341 Resource rdfResource, 342 final BaseRuntimeChildDefinition childDefinition, 343 final IBase element, 344 final String childName, 345 final BaseRuntimeElementDefinition<?> childDef, 346 final boolean includedResource, 347 final CompositeChildElement parent, 348 final EncodeContext encodeContext, 349 final Integer cardinalityIndex) { 350 351 String childGenericName = childDefinition.getElementName(); 352 353 encodeContext.pushPath(childGenericName, false); 354 try { 355 356 if (element == null || element.isEmpty()) { 357 if (!isChildContained(childDef, includedResource)) { 358 return rdfModel; 359 } 360 } 361 362 switch (childDef.getChildType()) { 363 case ID_DATATYPE: { 364 IIdType value = (IIdType) element; 365 assert value != null; 366 String encodedValue = ID.equals(childName) ? value.getIdPart() : value.getValue(); 367 if (StringUtils.isNotBlank(encodedValue) || !hasNoExtensions(value)) { 368 if (StringUtils.isNotBlank(encodedValue)) { 369 370 String propertyName = 371 constructPredicateName(resource, childDefinition, childName, parentElement); 372 if (element != null) { 373 XSDDatatype dataType = getXSDDataTypeForFhirType(element.fhirType(), encodedValue); 374 rdfResource.addProperty( 375 rdfModel.createProperty(propertyName), 376 this.createFhirValueBlankNode( 377 rdfModel, encodedValue, dataType, cardinalityIndex)); 378 } 379 } 380 } 381 break; 382 } 383 case PRIMITIVE_DATATYPE: { 384 IPrimitiveType<?> pd = (IPrimitiveType<?>) element; 385 assert pd != null; 386 String value = pd.getValueAsString(); 387 if (value != null || !hasNoExtensions(pd)) { 388 if (value != null) { 389 String propertyName = 390 constructPredicateName(resource, childDefinition, childName, parentElement); 391 XSDDatatype dataType = getXSDDataTypeForFhirType(pd.fhirType(), value); 392 Resource valueResource = 393 this.createFhirValueBlankNode(rdfModel, value, dataType, cardinalityIndex); 394 if (!hasNoExtensions(pd)) { 395 IBaseHasExtensions hasExtension = (IBaseHasExtensions) pd; 396 if (hasExtension.getExtension() != null 397 && hasExtension.getExtension().size() > 0) { 398 int i = 0; 399 for (IBaseExtension extension : hasExtension.getExtension()) { 400 RuntimeResourceDefinition resDef = 401 getContext().getResourceDefinition(resource); 402 Resource extensionResource = rdfModel.createResource(); 403 extensionResource.addProperty( 404 rdfModel.createProperty(FHIR_NS + FHIR_INDEX), 405 rdfModel.createTypedLiteral(i, XSDDatatype.XSDinteger)); 406 valueResource.addProperty( 407 rdfModel.createProperty(FHIR_NS + ELEMENT_EXTENSION), 408 extensionResource); 409 encodeCompositeElementToStreamWriter( 410 resource, 411 extension, 412 rdfModel, 413 extensionResource, 414 false, 415 new CompositeChildElement(resDef, encodeContext), 416 encodeContext); 417 } 418 } 419 } 420 421 rdfResource.addProperty(rdfModel.createProperty(propertyName), valueResource); 422 } 423 } 424 break; 425 } 426 case RESOURCE_BLOCK: 427 case COMPOSITE_DATATYPE: { 428 String idString = null; 429 String idPredicate = null; 430 if (element instanceof IBaseResource) { 431 idPredicate = FHIR_NS + RESOURCE_ID; 432 IIdType resourceId = processResourceID((IBaseResource) element, encodeContext); 433 if (resourceId != null) { 434 idString = resourceId.getIdPart(); 435 } 436 } else if (element instanceof IBaseElement) { 437 idPredicate = FHIR_NS + ELEMENT_ID; 438 if (((IBaseElement) element).getId() != null) { 439 idString = ((IBaseElement) element).getId(); 440 } 441 } 442 if (idString != null) { 443 rdfResource.addProperty( 444 rdfModel.createProperty(idPredicate), createFhirValueBlankNode(rdfModel, idString)); 445 } 446 rdfModel = encodeCompositeElementToStreamWriter( 447 resource, element, rdfModel, rdfResource, includedResource, parent, encodeContext); 448 break; 449 } 450 case CONTAINED_RESOURCE_LIST: 451 case CONTAINED_RESOURCES: { 452 if (element != null) { 453 IIdType resourceId = ((IBaseResource) element).getIdElement(); 454 Resource containedResource = rdfModel.createResource(); 455 rdfResource.addProperty( 456 rdfModel.createProperty(FHIR_NS + DOMAIN_RESOURCE_CONTAINED), containedResource); 457 if (cardinalityIndex != null) { 458 containedResource.addProperty( 459 rdfModel.createProperty(FHIR_NS + FHIR_INDEX), 460 cardinalityIndex.toString(), 461 XSDDatatype.XSDinteger); 462 } 463 encodeResourceToRDFStreamWriter( 464 (IBaseResource) element, 465 rdfModel, 466 true, 467 super.fixContainedResourceId(resourceId.getValue()), 468 encodeContext, 469 false, 470 containedResource); 471 } 472 break; 473 } 474 case RESOURCE: { 475 IBaseResource baseResource = (IBaseResource) element; 476 String resourceName = getContext().getResourceType(baseResource); 477 if (!super.shouldEncodeResource(resourceName)) { 478 break; 479 } 480 encodeContext.pushPath(resourceName, true); 481 IIdType resourceId = processResourceID(resource, encodeContext); 482 encodeResourceToRDFStreamWriter(resource, rdfModel, false, resourceId, encodeContext, false, null); 483 encodeContext.popPath(); 484 break; 485 } 486 case PRIMITIVE_XHTML: 487 case PRIMITIVE_XHTML_HL7ORG: { 488 IBaseXhtml xHtmlNode = (IBaseXhtml) element; 489 if (xHtmlNode != null) { 490 String value = xHtmlNode.getValueAsString(); 491 String propertyName = 492 constructPredicateName(resource, childDefinition, childName, parentElement); 493 rdfResource.addProperty(rdfModel.createProperty(propertyName), value); 494 } 495 break; 496 } 497 case EXTENSION_DECLARED: 498 case UNDECL_EXT: 499 default: { 500 throw new IllegalStateException( 501 Msg.code(1847) + "Unexpected node - should not happen: " + childDef.getName()); 502 } 503 } 504 } finally { 505 encodeContext.popPath(); 506 } 507 508 return rdfModel; 509 } 510 511 /** 512 * Maps hapi internal fhirType attribute to XSDDatatype enumeration 513 * @param fhirType hapi field type 514 * @return XSDDatatype value 515 */ 516 private XSDDatatype getXSDDataTypeForFhirType(String fhirType, String value) { 517 switch (fhirType) { 518 case "boolean": 519 return XSDDatatype.XSDboolean; 520 case "uri": 521 return XSDDatatype.XSDanyURI; 522 case "decimal": 523 return XSDDatatype.XSDdecimal; 524 case "date": 525 return XSDDatatype.XSDdate; 526 case "dateTime": 527 case "instant": 528 switch (value.length()) { // assumes valid lexical value 529 case 4: 530 return XSDDatatype.XSDgYear; 531 case 7: 532 return XSDDatatype.XSDgYearMonth; 533 case 10: 534 return XSDDatatype.XSDdate; 535 default: 536 return XSDDatatype.XSDdateTime; 537 } 538 case "code": 539 case "string": 540 default: 541 return XSDDatatype.XSDstring; 542 } 543 } 544 545 private IIdType processResourceID(final IBaseResource resource, final EncodeContext encodeContext) { 546 IIdType resourceId = null; 547 548 if (StringUtils.isNotBlank(resource.getIdElement().getIdPart())) { 549 resourceId = resource.getIdElement(); 550 if (resource.getIdElement().getValue().startsWith("urn:")) { 551 resourceId = null; 552 } 553 } 554 555 if (!super.shouldEncodeResourceId(resource, encodeContext)) { 556 resourceId = null; 557 } else if (encodeContext.getResourcePath().size() == 1 && super.getEncodeForceResourceId() != null) { 558 resourceId = super.getEncodeForceResourceId(); 559 } 560 561 return resourceId; 562 } 563 564 private Model encodeExtension( 565 final IBaseResource resource, 566 Model rdfModel, 567 Resource rdfResource, 568 final boolean containedResource, 569 final CompositeChildElement nextChildElem, 570 final BaseRuntimeChildDefinition nextChild, 571 final IBase nextValue, 572 final String childName, 573 final BaseRuntimeElementDefinition<?> childDef, 574 final EncodeContext encodeContext, 575 Integer cardinalityIndex) { 576 BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild; 577 578 Resource childResource = rdfModel.createResource(); 579 String extensionPredicateName = constructPredicateName(resource, extDef, extDef.getElementName(), null); 580 rdfResource.addProperty(rdfModel.createProperty(extensionPredicateName), childResource); 581 if (cardinalityIndex != null && cardinalityIndex > -1) { 582 childResource.addProperty( 583 rdfModel.createProperty(FHIR_NS + FHIR_INDEX), cardinalityIndex.toString(), XSDDatatype.XSDinteger); 584 } 585 586 rdfModel = encodeChildElementToStreamWriter( 587 resource, 588 null, 589 rdfModel, 590 childResource, 591 nextChild, 592 nextValue, 593 childName, 594 childDef, 595 containedResource, 596 nextChildElem, 597 encodeContext, 598 cardinalityIndex); 599 600 return rdfModel; 601 } 602 603 private Model encodeCompositeElementToStreamWriter( 604 final IBaseResource resource, 605 final IBase element, 606 Model rdfModel, 607 Resource rdfResource, 608 final boolean containedResource, 609 final CompositeChildElement parent, 610 final EncodeContext encodeContext) { 611 612 for (CompositeChildElement nextChildElem : 613 super.compositeChildIterator(element, containedResource, parent, encodeContext)) { 614 615 BaseRuntimeChildDefinition nextChild = nextChildElem.getDef(); 616 617 if (nextChild instanceof RuntimeChildNarrativeDefinition) { 618 INarrativeGenerator gen = getContext().getNarrativeGenerator(); 619 if (gen != null) { 620 INarrative narrative; 621 if (resource instanceof IResource) { 622 narrative = ((IResource) resource).getText(); 623 } else if (resource instanceof IDomainResource) { 624 narrative = ((IDomainResource) resource).getText(); 625 } else { 626 narrative = null; 627 } 628 assert narrative != null; 629 if (narrative.isEmpty()) { 630 gen.populateResourceNarrative(getContext(), resource); 631 } else { 632 RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild; 633 634 // This is where we populate the parent of the narrative 635 Resource childResource = rdfModel.createResource(); 636 637 String propertyName = constructPredicateName(resource, child, child.getElementName(), element); 638 rdfResource.addProperty(rdfModel.createProperty(propertyName), childResource); 639 640 String childName = nextChild.getChildNameByDatatype(child.getDatatype()); 641 BaseRuntimeElementDefinition<?> type = child.getChildByName(childName); 642 rdfModel = encodeChildElementToStreamWriter( 643 resource, 644 element, 645 rdfModel, 646 childResource, 647 nextChild, 648 narrative, 649 childName, 650 type, 651 containedResource, 652 nextChildElem, 653 encodeContext, 654 null); 655 continue; 656 } 657 } 658 } 659 660 if (nextChild instanceof RuntimeChildDirectResource) { 661 662 List<? extends IBase> values = nextChild.getAccessor().getValues(element); 663 if (values == null || values.isEmpty()) { 664 continue; 665 } 666 667 IBaseResource directChildResource = (IBaseResource) values.get(0); 668 // If it is a direct resource, we need to create a new subject for it. 669 Resource childResource = encodeResourceToRDFStreamWriter( 670 directChildResource, 671 rdfModel, 672 false, 673 directChildResource.getIdElement(), 674 encodeContext, 675 false, 676 null); 677 String propertyName = constructPredicateName(resource, nextChild, nextChild.getElementName(), element); 678 rdfResource.addProperty(rdfModel.createProperty(propertyName), childResource); 679 680 continue; 681 } 682 683 if (nextChild instanceof RuntimeChildContainedResources) { 684 List<? extends IBase> values = nextChild.getAccessor().getValues(element); 685 int i = 0; 686 for (IBase containedResourceEntity : values) { 687 rdfModel = encodeChildElementToStreamWriter( 688 resource, 689 element, 690 rdfModel, 691 rdfResource, 692 nextChild, 693 containedResourceEntity, 694 nextChild.getChildNameByDatatype(null), 695 nextChild.getChildElementDefinitionByDatatype(null), 696 containedResource, 697 nextChildElem, 698 encodeContext, 699 i); 700 i++; 701 } 702 } else { 703 704 List<? extends IBase> values = nextChild.getAccessor().getValues(element); 705 values = super.preProcessValues(nextChild, resource, values, nextChildElem, encodeContext); 706 707 if (values == null || values.isEmpty()) { 708 continue; 709 } 710 711 Integer cardinalityIndex = null; 712 int indexCounter = 0; 713 714 for (IBase nextValue : values) { 715 if (nextChild.getMax() != 1) { 716 cardinalityIndex = indexCounter; 717 indexCounter++; 718 } 719 if ((nextValue == null || nextValue.isEmpty())) { 720 continue; 721 } 722 723 ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue); 724 if (childNameAndDef == null) { 725 continue; 726 } 727 728 String childName = childNameAndDef.getChildName(); 729 BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef(); 730 String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl()); 731 732 if (extensionUrl != null && !childName.equals(EXTENSION)) { 733 rdfModel = encodeExtension( 734 resource, 735 rdfModel, 736 rdfResource, 737 containedResource, 738 nextChildElem, 739 nextChild, 740 nextValue, 741 childName, 742 childDef, 743 encodeContext, 744 cardinalityIndex); 745 } else if (nextChild instanceof RuntimeChildExtension) { 746 IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue; 747 if ((extension.getValue() == null 748 || extension.getValue().isEmpty())) { 749 if (extension.getExtension().isEmpty()) { 750 continue; 751 } 752 } 753 rdfModel = encodeExtension( 754 resource, 755 rdfModel, 756 rdfResource, 757 containedResource, 758 nextChildElem, 759 nextChild, 760 nextValue, 761 childName, 762 childDef, 763 encodeContext, 764 cardinalityIndex); 765 } else if (!(nextChild instanceof RuntimeChildNarrativeDefinition) || !containedResource) { 766 767 // If the child is not a value type, create a child object (blank node) for subordinate 768 // predicates to be attached to 769 if (childDef.getChildType() != PRIMITIVE_DATATYPE 770 && childDef.getChildType() != PRIMITIVE_XHTML_HL7ORG 771 && childDef.getChildType() != PRIMITIVE_XHTML 772 && childDef.getChildType() != ID_DATATYPE) { 773 Resource childResource = rdfModel.createResource(); 774 775 String propertyName = constructPredicateName(resource, nextChild, childName, nextValue); 776 rdfResource.addProperty(rdfModel.createProperty(propertyName), childResource); 777 if (cardinalityIndex != null && cardinalityIndex > -1) { 778 childResource.addProperty( 779 rdfModel.createProperty(FHIR_NS + FHIR_INDEX), 780 cardinalityIndex.toString(), 781 XSDDatatype.XSDinteger); 782 } 783 rdfModel = encodeChildElementToStreamWriter( 784 resource, 785 element, 786 rdfModel, 787 childResource, 788 nextChild, 789 nextValue, 790 childName, 791 childDef, 792 containedResource, 793 nextChildElem, 794 encodeContext, 795 cardinalityIndex); 796 } else { 797 rdfModel = encodeChildElementToStreamWriter( 798 resource, 799 element, 800 rdfModel, 801 rdfResource, 802 nextChild, 803 nextValue, 804 childName, 805 childDef, 806 containedResource, 807 nextChildElem, 808 encodeContext, 809 cardinalityIndex); 810 } 811 } 812 } 813 } 814 } 815 return rdfModel; 816 } 817 818 private <T extends IBaseResource> T parseResource(Class<T> resourceType, Model rdfModel) { 819 // jsonMode of true is passed in so that the xhtml parser state behaves as expected 820 // Push PreResourceState 821 ParserState<T> parserState = 822 ParserState.getPreResourceInstance(this, resourceType, getContext(), true, getErrorHandler()); 823 return parseRootResource(rdfModel, parserState, resourceType); 824 } 825 826 private <T> T parseRootResource(Model rdfModel, ParserState<T> parserState, Class<T> resourceType) { 827 logger.trace("Entering parseRootResource with state: {}", parserState); 828 829 StmtIterator rootStatementIterator = rdfModel.listStatements( 830 null, rdfModel.getProperty(FHIR_NS + NODE_ROLE), rdfModel.getProperty(FHIR_NS + TREE_ROOT)); 831 832 Resource rootResource; 833 String fhirResourceType, fhirTypeString; 834 while (rootStatementIterator.hasNext()) { 835 Statement rootStatement = rootStatementIterator.next(); 836 rootResource = rootStatement.getSubject(); 837 838 // If a resourceType is not provided via the server framework, discern it based on the rdf:type Arc 839 if (resourceType == null) { 840 Statement resourceTypeStatement = rootResource.getProperty(RDF.type); 841 fhirTypeString = resourceTypeStatement.getObject().toString(); 842 if (fhirTypeString.startsWith(FHIR_NS)) { 843 fhirTypeString = fhirTypeString.replace(FHIR_NS, ""); 844 } 845 } else { 846 fhirTypeString = resourceType.getSimpleName(); 847 } 848 849 RuntimeResourceDefinition definition = getContext().getResourceDefinition(fhirTypeString); 850 fhirResourceType = definition.getName(); 851 852 parseResource(parserState, fhirResourceType, rootResource); 853 854 // Pop PreResourceState 855 parserState.endingElement(); 856 } 857 return parserState.getObject(); 858 } 859 860 private <T> void parseResource(ParserState<T> parserState, String resourceType, RDFNode rootNode) { 861 // Push top-level entity 862 parserState.enteringNewElement(FHIR_NS, resourceType); 863 864 if (rootNode instanceof Resource) { 865 Resource rootResource = rootNode.asResource(); 866 List<Statement> statements = rootResource.listProperties().toList(); 867 statements.sort(new FhirIndexStatementComparator()); 868 for (Statement statement : statements) { 869 String predicateAttributeName = extractAttributeNameFromPredicate(statement); 870 if (predicateAttributeName != null) { 871 if (predicateAttributeName.equals(MODIFIER_EXTENSION)) { 872 processExtension(parserState, statement.getObject(), true); 873 } else if (predicateAttributeName.equals(EXTENSION)) { 874 processExtension(parserState, statement.getObject(), false); 875 } else { 876 processStatementObject(parserState, predicateAttributeName, statement.getObject()); 877 } 878 } 879 } 880 } else if (rootNode instanceof Literal) { 881 parserState.attributeValue(VALUE, rootNode.asLiteral().getString()); 882 } 883 884 // Pop top-level entity 885 parserState.endingElement(); 886 } 887 888 private String extractAttributeNameFromPredicate(Statement statement) { 889 String predicateUri = statement.getPredicate().getURI(); 890 891 // If the predicateURI is one we're ignoring, return null 892 // This minimizes 'Unknown Element' warnings in the parsing process 893 if (ignoredPredicates.contains(predicateUri)) { 894 return null; 895 } 896 897 String predicateObjectAttribute = predicateUri.substring(predicateUri.lastIndexOf("/") + 1); 898 String predicateAttributeName; 899 if (predicateObjectAttribute.contains(".")) { 900 predicateAttributeName = predicateObjectAttribute.substring(predicateObjectAttribute.lastIndexOf(".") + 1); 901 } else { 902 predicateAttributeName = predicateObjectAttribute; 903 } 904 return predicateAttributeName; 905 } 906 907 private <T> void processStatementObject( 908 ParserState<T> parserState, String predicateAttributeName, RDFNode statementObject) { 909 logger.trace( 910 "Entering processStatementObject with state: {}, for attribute {}", 911 parserState, 912 predicateAttributeName); 913 // Push attribute element 914 parserState.enteringNewElement(FHIR_NS, predicateAttributeName); 915 916 if (statementObject != null) { 917 if (statementObject.isLiteral()) { 918 // If the object is a literal, apply the value directly 919 parserState.attributeValue(VALUE, statementObject.asLiteral().getLexicalForm()); 920 } else if (statementObject.isAnon()) { 921 // If the object is a blank node, 922 Resource resourceObject = statementObject.asResource(); 923 924 boolean containedResource = false; 925 if (predicateAttributeName.equals(CONTAINED)) { 926 containedResource = true; 927 parserState.enteringNewElement( 928 FHIR_NS, 929 resourceObject 930 .getProperty(resourceObject.getModel().createProperty(RDF.type.getURI())) 931 .getObject() 932 .toString() 933 .replace(FHIR_NS, "")); 934 } 935 936 List<Statement> objectStatements = 937 resourceObject.listProperties().toList(); 938 objectStatements.sort(new FhirIndexStatementComparator()); 939 for (Statement objectProperty : objectStatements) { 940 if (objectProperty.getPredicate().hasURI(FHIR_NS + VALUE)) { 941 predicateAttributeName = VALUE; 942 parserState.attributeValue( 943 predicateAttributeName, 944 objectProperty.getObject().asLiteral().getLexicalForm()); 945 } else { 946 // Otherwise, process it as a net-new node 947 predicateAttributeName = extractAttributeNameFromPredicate(objectProperty); 948 if (predicateAttributeName != null) { 949 if (predicateAttributeName.equals(EXTENSION)) { 950 processExtension(parserState, objectProperty.getObject(), false); 951 } else if (predicateAttributeName.equals(MODIFIER_EXTENSION)) { 952 processExtension(parserState, objectProperty.getObject(), true); 953 } else { 954 processStatementObject(parserState, predicateAttributeName, objectProperty.getObject()); 955 } 956 } 957 } 958 } 959 960 if (containedResource) { 961 // Leave the contained resource element we created 962 parserState.endingElement(); 963 } 964 } else if (statementObject.isResource()) { 965 Resource innerResource = statementObject.asResource(); 966 Statement resourceTypeStatement = innerResource.getProperty(RDF.type); 967 String fhirTypeString = resourceTypeStatement.getObject().toString(); 968 if (fhirTypeString.startsWith(FHIR_NS)) { 969 fhirTypeString = fhirTypeString.replace(FHIR_NS, ""); 970 } 971 parseResource(parserState, fhirTypeString, innerResource); 972 } 973 } 974 975 // Pop attribute element 976 parserState.endingElement(); 977 } 978 979 private <T> void processExtension(ParserState<T> parserState, RDFNode statementObject, boolean isModifier) { 980 logger.trace("Entering processExtension with state: {}", parserState); 981 Resource resource = statementObject.asResource(); 982 Statement urlProperty = resource.getProperty(resource.getModel().createProperty(FHIR_NS + EXTENSION_URL)); 983 Resource urlPropertyResource = urlProperty.getObject().asResource(); 984 String extensionUrl = urlPropertyResource 985 .getProperty(resource.getModel().createProperty(FHIR_NS + VALUE)) 986 .getObject() 987 .asLiteral() 988 .getString(); 989 990 List<Statement> extensionStatements = resource.listProperties().toList(); 991 String extensionValueType = null; 992 RDFNode extensionValueResource = null; 993 for (Statement statement : extensionStatements) { 994 String propertyUri = statement.getPredicate().getURI(); 995 if (propertyUri.contains("Extension.value")) { 996 extensionValueType = propertyUri.replace(FHIR_NS + "Extension.", ""); 997 BaseRuntimeElementDefinition<?> target = getContext() 998 .getRuntimeChildUndeclaredExtensionDefinition() 999 .getChildByName(extensionValueType); 1000 if (target.getChildType().equals(ID_DATATYPE) 1001 || target.getChildType().equals(PRIMITIVE_DATATYPE)) { 1002 extensionValueResource = statement 1003 .getObject() 1004 .asResource() 1005 .getProperty(resource.getModel().createProperty(FHIR_NS + VALUE)) 1006 .getObject() 1007 .asLiteral(); 1008 } else { 1009 extensionValueResource = statement.getObject().asResource(); 1010 } 1011 break; 1012 } 1013 } 1014 1015 parserState.enteringNewElementExtension(null, extensionUrl, isModifier, null); 1016 // Some extensions don't have their own values - they then have more extensions inside of them 1017 if (extensionValueType != null) { 1018 parseResource(parserState, extensionValueType, extensionValueResource); 1019 } 1020 1021 for (Statement statement : extensionStatements) { 1022 String propertyUri = statement.getPredicate().getURI(); 1023 if (propertyUri.equals(FHIR_NS + ELEMENT_EXTENSION)) { 1024 processExtension(parserState, statement.getObject(), false); 1025 } 1026 } 1027 1028 parserState.endingElement(); 1029 } 1030 1031 static class FhirIndexStatementComparator implements Comparator<Statement> { 1032 1033 @Override 1034 public int compare(Statement arg0, Statement arg1) { 1035 int result = 1036 arg0.getPredicate().getURI().compareTo(arg1.getPredicate().getURI()); 1037 if (result == 0) { 1038 if (arg0.getObject().isResource() && arg1.getObject().isResource()) { 1039 Resource resource0 = arg0.getObject().asResource(); 1040 Resource resource1 = arg1.getObject().asResource(); 1041 1042 result = Integer.compare(getFhirIndex(resource0), getFhirIndex(resource1)); 1043 } 1044 } 1045 return result; 1046 } 1047 1048 private int getFhirIndex(Resource resource) { 1049 if (resource.hasProperty(resource.getModel().createProperty(FHIR_NS + FHIR_INDEX))) { 1050 return resource.getProperty(resource.getModel().createProperty(FHIR_NS + FHIR_INDEX)) 1051 .getInt(); 1052 } 1053 return -1; 1054 } 1055 } 1056}