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