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