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