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