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, encodeContext); 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 theEncodeContext, 349 final Integer cardinalityIndex) { 350 351 String childGenericName = childDefinition.getElementName(); 352 353 theEncodeContext.pushPath(childGenericName, false); 354 try { 355 356 if (element == null || element.isEmpty()) { 357 if (!isChildContained(childDef, includedResource, theEncodeContext)) { 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, theEncodeContext), 416 theEncodeContext); 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, theEncodeContext); 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, theEncodeContext); 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 theEncodeContext, 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, theEncodeContext)) { 478 break; 479 } 480 theEncodeContext.pushPath(resourceName, true); 481 IIdType resourceId = processResourceID(resource, theEncodeContext); 482 encodeResourceToRDFStreamWriter( 483 resource, rdfModel, false, resourceId, theEncodeContext, false, null); 484 theEncodeContext.popPath(); 485 break; 486 } 487 case PRIMITIVE_XHTML: 488 case PRIMITIVE_XHTML_HL7ORG: { 489 IBaseXhtml xHtmlNode = (IBaseXhtml) element; 490 if (xHtmlNode != null) { 491 String value = xHtmlNode.getValueAsString(); 492 String propertyName = 493 constructPredicateName(resource, childDefinition, childName, parentElement); 494 rdfResource.addProperty(rdfModel.createProperty(propertyName), value); 495 } 496 break; 497 } 498 case EXTENSION_DECLARED: 499 case UNDECL_EXT: 500 default: { 501 throw new IllegalStateException( 502 Msg.code(1847) + "Unexpected node - should not happen: " + childDef.getName()); 503 } 504 } 505 } finally { 506 theEncodeContext.popPath(); 507 } 508 509 return rdfModel; 510 } 511 512 /** 513 * Maps hapi internal fhirType attribute to XSDDatatype enumeration 514 * @param fhirType hapi field type 515 * @return XSDDatatype value 516 */ 517 private XSDDatatype getXSDDataTypeForFhirType(String fhirType, String value) { 518 switch (fhirType) { 519 case "boolean": 520 return XSDDatatype.XSDboolean; 521 case "uri": 522 return XSDDatatype.XSDanyURI; 523 case "decimal": 524 return XSDDatatype.XSDdecimal; 525 case "date": 526 return XSDDatatype.XSDdate; 527 case "dateTime": 528 case "instant": 529 switch (value.length()) { // assumes valid lexical value 530 case 4: 531 return XSDDatatype.XSDgYear; 532 case 7: 533 return XSDDatatype.XSDgYearMonth; 534 case 10: 535 return XSDDatatype.XSDdate; 536 default: 537 return XSDDatatype.XSDdateTime; 538 } 539 case "code": 540 case "string": 541 default: 542 return XSDDatatype.XSDstring; 543 } 544 } 545 546 private IIdType processResourceID(final IBaseResource resource, final EncodeContext encodeContext) { 547 IIdType resourceId = null; 548 549 if (StringUtils.isNotBlank(resource.getIdElement().getIdPart())) { 550 resourceId = resource.getIdElement(); 551 if (resource.getIdElement().getValue().startsWith("urn:")) { 552 resourceId = null; 553 } 554 } 555 556 if (!super.shouldEncodeResourceId(resource, encodeContext)) { 557 resourceId = null; 558 } else if (encodeContext.getResourcePath().size() == 1 && super.getEncodeForceResourceId() != null) { 559 resourceId = super.getEncodeForceResourceId(); 560 } 561 562 return resourceId; 563 } 564 565 private Model encodeExtension( 566 final IBaseResource resource, 567 Model rdfModel, 568 Resource rdfResource, 569 final boolean containedResource, 570 final CompositeChildElement nextChildElem, 571 final BaseRuntimeChildDefinition nextChild, 572 final IBase nextValue, 573 final String childName, 574 final BaseRuntimeElementDefinition<?> childDef, 575 final EncodeContext encodeContext, 576 Integer cardinalityIndex) { 577 BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild; 578 579 Resource childResource = rdfModel.createResource(); 580 String extensionPredicateName = constructPredicateName(resource, extDef, extDef.getElementName(), null); 581 rdfResource.addProperty(rdfModel.createProperty(extensionPredicateName), childResource); 582 if (cardinalityIndex != null && cardinalityIndex > -1) { 583 childResource.addProperty( 584 rdfModel.createProperty(FHIR_NS + FHIR_INDEX), cardinalityIndex.toString(), XSDDatatype.XSDinteger); 585 } 586 587 rdfModel = encodeChildElementToStreamWriter( 588 resource, 589 null, 590 rdfModel, 591 childResource, 592 nextChild, 593 nextValue, 594 childName, 595 childDef, 596 containedResource, 597 nextChildElem, 598 encodeContext, 599 cardinalityIndex); 600 601 return rdfModel; 602 } 603 604 private Model encodeCompositeElementToStreamWriter( 605 final IBaseResource resource, 606 final IBase element, 607 Model rdfModel, 608 Resource rdfResource, 609 final boolean containedResource, 610 final CompositeChildElement parent, 611 final EncodeContext encodeContext) { 612 613 for (CompositeChildElement nextChildElem : 614 super.compositeChildIterator(element, containedResource, parent, encodeContext)) { 615 616 BaseRuntimeChildDefinition nextChild = nextChildElem.getDef(); 617 618 if (nextChild instanceof RuntimeChildNarrativeDefinition) { 619 INarrativeGenerator gen = getContext().getNarrativeGenerator(); 620 if (gen != null) { 621 INarrative narrative; 622 if (resource instanceof IResource) { 623 narrative = ((IResource) resource).getText(); 624 } else if (resource instanceof IDomainResource) { 625 narrative = ((IDomainResource) resource).getText(); 626 } else { 627 narrative = null; 628 } 629 assert narrative != null; 630 if (narrative.isEmpty()) { 631 gen.populateResourceNarrative(getContext(), resource); 632 } else { 633 RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild; 634 635 // This is where we populate the parent of the narrative 636 Resource childResource = rdfModel.createResource(); 637 638 String propertyName = constructPredicateName(resource, child, child.getElementName(), element); 639 rdfResource.addProperty(rdfModel.createProperty(propertyName), childResource); 640 641 String childName = nextChild.getChildNameByDatatype(child.getDatatype()); 642 BaseRuntimeElementDefinition<?> type = child.getChildByName(childName); 643 rdfModel = encodeChildElementToStreamWriter( 644 resource, 645 element, 646 rdfModel, 647 childResource, 648 nextChild, 649 narrative, 650 childName, 651 type, 652 containedResource, 653 nextChildElem, 654 encodeContext, 655 null); 656 continue; 657 } 658 } 659 } 660 661 if (nextChild instanceof RuntimeChildDirectResource) { 662 663 List<? extends IBase> values = nextChild.getAccessor().getValues(element); 664 if (values == null || values.isEmpty()) { 665 continue; 666 } 667 668 IBaseResource directChildResource = (IBaseResource) values.get(0); 669 // If it is a direct resource, we need to create a new subject for it. 670 Resource childResource = encodeResourceToRDFStreamWriter( 671 directChildResource, 672 rdfModel, 673 false, 674 directChildResource.getIdElement(), 675 encodeContext, 676 false, 677 null); 678 String propertyName = constructPredicateName(resource, nextChild, nextChild.getElementName(), element); 679 rdfResource.addProperty(rdfModel.createProperty(propertyName), childResource); 680 681 continue; 682 } 683 684 if (nextChild instanceof RuntimeChildContainedResources) { 685 List<? extends IBase> values = nextChild.getAccessor().getValues(element); 686 int i = 0; 687 for (IBase containedResourceEntity : values) { 688 rdfModel = encodeChildElementToStreamWriter( 689 resource, 690 element, 691 rdfModel, 692 rdfResource, 693 nextChild, 694 containedResourceEntity, 695 nextChild.getChildNameByDatatype(null), 696 nextChild.getChildElementDefinitionByDatatype(null), 697 containedResource, 698 nextChildElem, 699 encodeContext, 700 i); 701 i++; 702 } 703 } else { 704 705 List<? extends IBase> values = nextChild.getAccessor().getValues(element); 706 values = super.preProcessValues(nextChild, resource, values, nextChildElem, encodeContext); 707 708 if (values == null || values.isEmpty()) { 709 continue; 710 } 711 712 Integer cardinalityIndex = null; 713 int indexCounter = 0; 714 715 for (IBase nextValue : values) { 716 if (nextChild.getMax() != 1) { 717 cardinalityIndex = indexCounter; 718 indexCounter++; 719 } 720 if ((nextValue == null || nextValue.isEmpty())) { 721 continue; 722 } 723 724 ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue); 725 if (childNameAndDef == null) { 726 continue; 727 } 728 729 String childName = childNameAndDef.getChildName(); 730 BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef(); 731 String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl()); 732 733 if (extensionUrl != null && !childName.equals(EXTENSION)) { 734 rdfModel = encodeExtension( 735 resource, 736 rdfModel, 737 rdfResource, 738 containedResource, 739 nextChildElem, 740 nextChild, 741 nextValue, 742 childName, 743 childDef, 744 encodeContext, 745 cardinalityIndex); 746 } else if (nextChild instanceof RuntimeChildExtension) { 747 IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue; 748 if ((extension.getValue() == null 749 || extension.getValue().isEmpty())) { 750 if (extension.getExtension().isEmpty()) { 751 continue; 752 } 753 } 754 rdfModel = encodeExtension( 755 resource, 756 rdfModel, 757 rdfResource, 758 containedResource, 759 nextChildElem, 760 nextChild, 761 nextValue, 762 childName, 763 childDef, 764 encodeContext, 765 cardinalityIndex); 766 } else if (!(nextChild instanceof RuntimeChildNarrativeDefinition) || !containedResource) { 767 768 // If the child is not a value type, create a child object (blank node) for subordinate 769 // predicates to be attached to 770 if (childDef.getChildType() != PRIMITIVE_DATATYPE 771 && childDef.getChildType() != PRIMITIVE_XHTML_HL7ORG 772 && childDef.getChildType() != PRIMITIVE_XHTML 773 && childDef.getChildType() != ID_DATATYPE) { 774 Resource childResource = rdfModel.createResource(); 775 776 String propertyName = constructPredicateName(resource, nextChild, childName, nextValue); 777 rdfResource.addProperty(rdfModel.createProperty(propertyName), childResource); 778 if (cardinalityIndex != null && cardinalityIndex > -1) { 779 childResource.addProperty( 780 rdfModel.createProperty(FHIR_NS + FHIR_INDEX), 781 cardinalityIndex.toString(), 782 XSDDatatype.XSDinteger); 783 } 784 rdfModel = encodeChildElementToStreamWriter( 785 resource, 786 element, 787 rdfModel, 788 childResource, 789 nextChild, 790 nextValue, 791 childName, 792 childDef, 793 containedResource, 794 nextChildElem, 795 encodeContext, 796 cardinalityIndex); 797 } else { 798 rdfModel = encodeChildElementToStreamWriter( 799 resource, 800 element, 801 rdfModel, 802 rdfResource, 803 nextChild, 804 nextValue, 805 childName, 806 childDef, 807 containedResource, 808 nextChildElem, 809 encodeContext, 810 cardinalityIndex); 811 } 812 } 813 } 814 } 815 } 816 return rdfModel; 817 } 818 819 private <T extends IBaseResource> T parseResource(Class<T> resourceType, Model rdfModel) { 820 // jsonMode of true is passed in so that the xhtml parser state behaves as expected 821 // Push PreResourceState 822 ParserState<T> parserState = 823 ParserState.getPreResourceInstance(this, resourceType, getContext(), true, getErrorHandler()); 824 return parseRootResource(rdfModel, parserState, resourceType); 825 } 826 827 private <T> T parseRootResource(Model rdfModel, ParserState<T> parserState, Class<T> resourceType) { 828 logger.trace("Entering parseRootResource with state: {}", parserState); 829 830 StmtIterator rootStatementIterator = rdfModel.listStatements( 831 null, rdfModel.getProperty(FHIR_NS + NODE_ROLE), rdfModel.getProperty(FHIR_NS + TREE_ROOT)); 832 833 Resource rootResource; 834 String fhirResourceType, fhirTypeString; 835 while (rootStatementIterator.hasNext()) { 836 Statement rootStatement = rootStatementIterator.next(); 837 rootResource = rootStatement.getSubject(); 838 839 // If a resourceType is not provided via the server framework, discern it based on the rdf:type Arc 840 if (resourceType == null) { 841 Statement resourceTypeStatement = rootResource.getProperty(RDF.type); 842 fhirTypeString = resourceTypeStatement.getObject().toString(); 843 if (fhirTypeString.startsWith(FHIR_NS)) { 844 fhirTypeString = fhirTypeString.replace(FHIR_NS, ""); 845 } 846 } else { 847 fhirTypeString = resourceType.getSimpleName(); 848 } 849 850 RuntimeResourceDefinition definition = getContext().getResourceDefinition(fhirTypeString); 851 fhirResourceType = definition.getName(); 852 853 parseResource(parserState, fhirResourceType, rootResource); 854 855 // Pop PreResourceState 856 parserState.endingElement(); 857 } 858 return parserState.getObject(); 859 } 860 861 private <T> void parseResource(ParserState<T> parserState, String resourceType, RDFNode rootNode) { 862 // Push top-level entity 863 parserState.enteringNewElement(FHIR_NS, resourceType); 864 865 if (rootNode instanceof Resource) { 866 Resource rootResource = rootNode.asResource(); 867 List<Statement> statements = rootResource.listProperties().toList(); 868 statements.sort(new FhirIndexStatementComparator()); 869 for (Statement statement : statements) { 870 String predicateAttributeName = extractAttributeNameFromPredicate(statement); 871 if (predicateAttributeName != null) { 872 if (predicateAttributeName.equals(MODIFIER_EXTENSION)) { 873 processExtension(parserState, statement.getObject(), true); 874 } else if (predicateAttributeName.equals(EXTENSION)) { 875 processExtension(parserState, statement.getObject(), false); 876 } else { 877 processStatementObject(parserState, predicateAttributeName, statement.getObject()); 878 } 879 } 880 } 881 } else if (rootNode instanceof Literal) { 882 parserState.attributeValue(VALUE, rootNode.asLiteral().getString()); 883 } 884 885 // Pop top-level entity 886 parserState.endingElement(); 887 } 888 889 private String extractAttributeNameFromPredicate(Statement statement) { 890 String predicateUri = statement.getPredicate().getURI(); 891 892 // If the predicateURI is one we're ignoring, return null 893 // This minimizes 'Unknown Element' warnings in the parsing process 894 if (ignoredPredicates.contains(predicateUri)) { 895 return null; 896 } 897 898 String predicateObjectAttribute = predicateUri.substring(predicateUri.lastIndexOf("/") + 1); 899 String predicateAttributeName; 900 if (predicateObjectAttribute.contains(".")) { 901 predicateAttributeName = predicateObjectAttribute.substring(predicateObjectAttribute.lastIndexOf(".") + 1); 902 } else { 903 predicateAttributeName = predicateObjectAttribute; 904 } 905 return predicateAttributeName; 906 } 907 908 private <T> void processStatementObject( 909 ParserState<T> parserState, String predicateAttributeName, RDFNode statementObject) { 910 logger.trace( 911 "Entering processStatementObject with state: {}, for attribute {}", 912 parserState, 913 predicateAttributeName); 914 // Push attribute element 915 parserState.enteringNewElement(FHIR_NS, predicateAttributeName); 916 917 if (statementObject != null) { 918 if (statementObject.isLiteral()) { 919 // If the object is a literal, apply the value directly 920 parserState.attributeValue(VALUE, statementObject.asLiteral().getLexicalForm()); 921 } else if (statementObject.isAnon()) { 922 // If the object is a blank node, 923 Resource resourceObject = statementObject.asResource(); 924 925 boolean containedResource = false; 926 if (predicateAttributeName.equals(CONTAINED)) { 927 containedResource = true; 928 parserState.enteringNewElement( 929 FHIR_NS, 930 resourceObject 931 .getProperty(resourceObject.getModel().createProperty(RDF.type.getURI())) 932 .getObject() 933 .toString() 934 .replace(FHIR_NS, "")); 935 } 936 937 List<Statement> objectStatements = 938 resourceObject.listProperties().toList(); 939 objectStatements.sort(new FhirIndexStatementComparator()); 940 for (Statement objectProperty : objectStatements) { 941 if (objectProperty.getPredicate().hasURI(FHIR_NS + VALUE)) { 942 predicateAttributeName = VALUE; 943 parserState.attributeValue( 944 predicateAttributeName, 945 objectProperty.getObject().asLiteral().getLexicalForm()); 946 } else { 947 // Otherwise, process it as a net-new node 948 predicateAttributeName = extractAttributeNameFromPredicate(objectProperty); 949 if (predicateAttributeName != null) { 950 if (predicateAttributeName.equals(EXTENSION)) { 951 processExtension(parserState, objectProperty.getObject(), false); 952 } else if (predicateAttributeName.equals(MODIFIER_EXTENSION)) { 953 processExtension(parserState, objectProperty.getObject(), true); 954 } else { 955 processStatementObject(parserState, predicateAttributeName, objectProperty.getObject()); 956 } 957 } 958 } 959 } 960 961 if (containedResource) { 962 // Leave the contained resource element we created 963 parserState.endingElement(); 964 } 965 } else if (statementObject.isResource()) { 966 Resource innerResource = statementObject.asResource(); 967 Statement resourceTypeStatement = innerResource.getProperty(RDF.type); 968 String fhirTypeString = resourceTypeStatement.getObject().toString(); 969 if (fhirTypeString.startsWith(FHIR_NS)) { 970 fhirTypeString = fhirTypeString.replace(FHIR_NS, ""); 971 } 972 parseResource(parserState, fhirTypeString, innerResource); 973 } 974 } 975 976 // Pop attribute element 977 parserState.endingElement(); 978 } 979 980 private <T> void processExtension(ParserState<T> parserState, RDFNode statementObject, boolean isModifier) { 981 logger.trace("Entering processExtension with state: {}", parserState); 982 Resource resource = statementObject.asResource(); 983 Statement urlProperty = resource.getProperty(resource.getModel().createProperty(FHIR_NS + EXTENSION_URL)); 984 Resource urlPropertyResource = urlProperty.getObject().asResource(); 985 String extensionUrl = urlPropertyResource 986 .getProperty(resource.getModel().createProperty(FHIR_NS + VALUE)) 987 .getObject() 988 .asLiteral() 989 .getString(); 990 991 List<Statement> extensionStatements = resource.listProperties().toList(); 992 String extensionValueType = null; 993 RDFNode extensionValueResource = null; 994 for (Statement statement : extensionStatements) { 995 String propertyUri = statement.getPredicate().getURI(); 996 if (propertyUri.contains("Extension.value")) { 997 extensionValueType = propertyUri.replace(FHIR_NS + "Extension.", ""); 998 BaseRuntimeElementDefinition<?> target = getContext() 999 .getRuntimeChildUndeclaredExtensionDefinition() 1000 .getChildByName(extensionValueType); 1001 if (target.getChildType().equals(ID_DATATYPE) 1002 || target.getChildType().equals(PRIMITIVE_DATATYPE)) { 1003 extensionValueResource = statement 1004 .getObject() 1005 .asResource() 1006 .getProperty(resource.getModel().createProperty(FHIR_NS + VALUE)) 1007 .getObject() 1008 .asLiteral(); 1009 } else { 1010 extensionValueResource = statement.getObject().asResource(); 1011 } 1012 break; 1013 } 1014 } 1015 1016 parserState.enteringNewElementExtension(null, extensionUrl, isModifier, null); 1017 // Some extensions don't have their own values - they then have more extensions inside of them 1018 if (extensionValueType != null) { 1019 parseResource(parserState, extensionValueType, extensionValueResource); 1020 } 1021 1022 for (Statement statement : extensionStatements) { 1023 String propertyUri = statement.getPredicate().getURI(); 1024 if (propertyUri.equals(FHIR_NS + ELEMENT_EXTENSION)) { 1025 processExtension(parserState, statement.getObject(), false); 1026 } 1027 } 1028 1029 parserState.endingElement(); 1030 } 1031 1032 static class FhirIndexStatementComparator implements Comparator<Statement> { 1033 1034 @Override 1035 public int compare(Statement arg0, Statement arg1) { 1036 int result = 1037 arg0.getPredicate().getURI().compareTo(arg1.getPredicate().getURI()); 1038 if (result == 0) { 1039 if (arg0.getObject().isResource() && arg1.getObject().isResource()) { 1040 Resource resource0 = arg0.getObject().asResource(); 1041 Resource resource1 = arg1.getObject().asResource(); 1042 1043 result = Integer.compare(getFhirIndex(resource0), getFhirIndex(resource1)); 1044 } 1045 } 1046 return result; 1047 } 1048 1049 private int getFhirIndex(Resource resource) { 1050 if (resource.hasProperty(resource.getModel().createProperty(FHIR_NS + FHIR_INDEX))) { 1051 return resource.getProperty(resource.getModel().createProperty(FHIR_NS + FHIR_INDEX)) 1052 .getInt(); 1053 } 1054 return -1; 1055 } 1056 } 1057}