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