001/*- 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2024 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.util; 021 022import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; 024import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 025import ca.uhn.fhir.context.FhirContext; 026import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; 027import ca.uhn.fhir.context.RuntimeResourceDefinition; 028import ca.uhn.fhir.i18n.Msg; 029import org.apache.commons.lang3.StringUtils; 030import org.apache.commons.lang3.Validate; 031import org.apache.commons.lang3.tuple.Triple; 032import org.hl7.fhir.instance.model.api.IBase; 033import org.hl7.fhir.instance.model.api.IBaseBackboneElement; 034import org.hl7.fhir.instance.model.api.IBaseHasExtensions; 035import org.hl7.fhir.instance.model.api.IBaseResource; 036import org.hl7.fhir.instance.model.api.IPrimitiveType; 037import org.slf4j.Logger; 038 039import java.lang.reflect.Method; 040import java.util.Arrays; 041import java.util.Collection; 042import java.util.Collections; 043import java.util.List; 044import java.util.function.Predicate; 045import java.util.stream.Collectors; 046import java.util.stream.Stream; 047 048import static org.slf4j.LoggerFactory.getLogger; 049 050public final class TerserUtil { 051 052 public static final String FIELD_NAME_IDENTIFIER = "identifier"; 053 /** 054 * Exclude for id, identifier and meta fields of a resource. 055 */ 056 public static final Collection<String> IDS_AND_META_EXCLUDES = 057 Collections.unmodifiableSet(Stream.of("id", "identifier", "meta").collect(Collectors.toSet())); 058 /** 059 * Exclusion predicate for id, identifier, meta fields. 060 */ 061 public static final Predicate<String> EXCLUDE_IDS_AND_META = new Predicate<String>() { 062 @Override 063 public boolean test(String s) { 064 return !IDS_AND_META_EXCLUDES.contains(s); 065 } 066 }; 067 /** 068 * Exclusion predicate for id/identifier, meta and fields with empty values. This ensures that source / target resources, 069 * empty source fields will not results in erasure of target fields. 070 */ 071 public static final Predicate<Triple<BaseRuntimeChildDefinition, IBase, IBase>> EXCLUDE_IDS_META_AND_EMPTY = 072 new Predicate<Triple<BaseRuntimeChildDefinition, IBase, IBase>>() { 073 @Override 074 public boolean test(Triple<BaseRuntimeChildDefinition, IBase, IBase> theTriple) { 075 if (!EXCLUDE_IDS_AND_META.test(theTriple.getLeft().getElementName())) { 076 return false; 077 } 078 BaseRuntimeChildDefinition childDefinition = theTriple.getLeft(); 079 boolean isSourceFieldEmpty = childDefinition 080 .getAccessor() 081 .getValues(theTriple.getMiddle()) 082 .isEmpty(); 083 return !isSourceFieldEmpty; 084 } 085 }; 086 /** 087 * Exclusion predicate for keeping all fields. 088 */ 089 public static final Predicate<String> INCLUDE_ALL = new Predicate<String>() { 090 @Override 091 public boolean test(String s) { 092 return true; 093 } 094 }; 095 096 private static final Logger ourLog = getLogger(TerserUtil.class); 097 private static final String EQUALS_DEEP = "equalsDeep"; 098 public static final String DATA_ABSENT_REASON_EXTENSION_URI = 099 "http://hl7.org/fhir/StructureDefinition/data-absent-reason"; 100 101 private TerserUtil() {} 102 103 /** 104 * Given an Child Definition of `identifier`, a R4/DSTU3 Identifier, and a new resource, clone the identifier into that resources' identifier list if it is not already present. 105 */ 106 public static void cloneIdentifierIntoResource( 107 FhirContext theFhirContext, 108 BaseRuntimeChildDefinition theIdentifierDefinition, 109 IBase theNewIdentifier, 110 IBaseResource theResourceToCloneInto) { 111 // FHIR choice types - fields within fhir where we have a choice of ids 112 BaseRuntimeElementCompositeDefinition<?> childIdentifierElementDefinition = 113 (BaseRuntimeElementCompositeDefinition<?>) 114 theIdentifierDefinition.getChildByName(FIELD_NAME_IDENTIFIER); 115 116 List<IBase> existingIdentifiers = getValues(theFhirContext, theResourceToCloneInto, FIELD_NAME_IDENTIFIER); 117 if (existingIdentifiers != null) { 118 for (IBase existingIdentifier : existingIdentifiers) { 119 if (equals(existingIdentifier, theNewIdentifier)) { 120 ourLog.trace( 121 "Identifier {} already exists in resource {}", theNewIdentifier, theResourceToCloneInto); 122 return; 123 } 124 } 125 } 126 127 IBase newIdentifierBase = childIdentifierElementDefinition.newInstance(); 128 129 FhirTerser terser = theFhirContext.newTerser(); 130 terser.cloneInto(theNewIdentifier, newIdentifierBase, true); 131 theIdentifierDefinition.getMutator().addValue(theResourceToCloneInto, newIdentifierBase); 132 } 133 134 /** 135 * Checks if the specified fields has any values 136 * 137 * @param theFhirContext Context holding resource definition 138 * @param theResource Resource to check if the specified field is set 139 * @param theFieldName name of the field to check 140 * @return Returns true if field exists and has any values set, and false otherwise 141 */ 142 public static boolean hasValues(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) { 143 RuntimeResourceDefinition resourceDefinition = theFhirContext.getResourceDefinition(theResource); 144 BaseRuntimeChildDefinition resourceIdentifier = resourceDefinition.getChildByName(theFieldName); 145 if (resourceIdentifier == null) { 146 return false; 147 } 148 return !(resourceIdentifier.getAccessor().getValues(theResource).isEmpty()); 149 } 150 151 /** 152 * Gets all values of the specified field. 153 * 154 * @param theFhirContext Context holding resource definition 155 * @param theResource Resource to check if the specified field is set 156 * @param theFieldName name of the field to check 157 * @return Returns all values for the specified field or null if field with the provided name doesn't exist 158 */ 159 public static List<IBase> getValues(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) { 160 RuntimeResourceDefinition resourceDefinition = theFhirContext.getResourceDefinition(theResource); 161 BaseRuntimeChildDefinition resourceIdentifier = resourceDefinition.getChildByName(theFieldName); 162 if (resourceIdentifier == null) { 163 ourLog.info("There is no field named {} in Resource {}", theFieldName, resourceDefinition.getName()); 164 return null; 165 } 166 return resourceIdentifier.getAccessor().getValues(theResource); 167 } 168 169 /** 170 * Gets the first available value for the specified field. 171 * 172 * @param theFhirContext Context holding resource definition 173 * @param theResource Resource to check if the specified field is set 174 * @param theFieldName name of the field to check 175 * @return Returns the first value for the specified field or null if field with the provided name doesn't exist or 176 * has no values 177 */ 178 public static IBase getValueFirstRep(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) { 179 List<IBase> values = getValues(theFhirContext, theResource, theFieldName); 180 if (values == null || values.isEmpty()) { 181 return null; 182 } 183 return values.get(0); 184 } 185 186 /** 187 * Clones specified composite field (collection). Composite field values must conform to the collections 188 * contract. 189 * 190 * @param theFrom Resource to clone the specified field from 191 * @param theTo Resource to clone the specified field to 192 * @param theField Field name to be copied 193 */ 194 public static void cloneCompositeField( 195 FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo, String theField) { 196 FhirTerser terser = theFhirContext.newTerser(); 197 198 RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom); 199 BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theField); 200 Validate.notNull(childDefinition); 201 202 List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theFrom); 203 List<IBase> theToFieldValues = childDefinition.getAccessor().getValues(theTo); 204 205 for (IBase theFromFieldValue : theFromFieldValues) { 206 if (containsPrimitiveValue(theFromFieldValue, theToFieldValues)) { 207 continue; 208 } 209 210 IBase newFieldValue = newElement(terser, childDefinition, theFromFieldValue, null); 211 terser.cloneInto(theFromFieldValue, newFieldValue, true); 212 213 try { 214 theToFieldValues.add(newFieldValue); 215 } catch (Exception e) { 216 childDefinition.getMutator().setValue(theTo, newFieldValue); 217 } 218 } 219 } 220 221 private static boolean containsPrimitiveValue(IBase theItem, List<IBase> theItems) { 222 PrimitiveTypeEqualsPredicate predicate = new PrimitiveTypeEqualsPredicate(); 223 return theItems.stream().anyMatch(i -> { 224 return predicate.test(i, theItem); 225 }); 226 } 227 228 private static Method getMethod(IBase theBase, String theMethodName) { 229 Method method = null; 230 for (Method m : theBase.getClass().getDeclaredMethods()) { 231 if (m.getName().equals(theMethodName)) { 232 method = m; 233 break; 234 } 235 } 236 return method; 237 } 238 239 /** 240 * Checks if two items are equal via {@link #EQUALS_DEEP} method 241 * 242 * @param theItem1 First item to compare 243 * @param theItem2 Second item to compare 244 * @return Returns true if they are equal and false otherwise 245 */ 246 public static boolean equals(IBase theItem1, IBase theItem2) { 247 if (theItem1 == null) { 248 return theItem2 == null; 249 } 250 251 final Method method = getMethod(theItem1, EQUALS_DEEP); 252 Validate.notNull(method); 253 return equals(theItem1, theItem2, method); 254 } 255 256 private static boolean equals(IBase theItem1, IBase theItem2, Method theMethod) { 257 if (theMethod != null) { 258 try { 259 return (Boolean) theMethod.invoke(theItem1, theItem2); 260 } catch (Exception e) { 261 throw new RuntimeException( 262 Msg.code(1746) + String.format("Unable to compare equality via %s", EQUALS_DEEP), e); 263 } 264 } 265 return theItem1.equals(theItem2); 266 } 267 268 private static boolean contains(IBase theItem, List<IBase> theItems) { 269 final Method method = getMethod(theItem, EQUALS_DEEP); 270 return theItems.stream().anyMatch(i -> equals(i, theItem, method)); 271 } 272 273 private static boolean hasDataAbsentReason(IBase theItem) { 274 if (theItem instanceof IBaseHasExtensions) { 275 IBaseHasExtensions hasExtensions = (IBaseHasExtensions) theItem; 276 return hasExtensions.getExtension().stream() 277 .anyMatch(t -> StringUtils.equals(t.getUrl(), DATA_ABSENT_REASON_EXTENSION_URI)); 278 } 279 return false; 280 } 281 282 /** 283 * Merges all fields on the provided instance. <code>theTo</code> will contain a union of all values from <code>theFrom</code> 284 * instance and <code>theTo</code> instance. 285 * 286 * @param theFhirContext Context holding resource definition 287 * @param theFrom The resource to merge the fields from 288 * @param theTo The resource to merge the fields into 289 */ 290 public static void mergeAllFields(FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo) { 291 mergeFields(theFhirContext, theFrom, theTo, INCLUDE_ALL); 292 } 293 294 /** 295 * Replaces all fields that have matching field names by the given inclusion strategy. <code>theTo</code> will contain a copy of the 296 * values from <code>theFrom</code> instance. 297 * 298 * @param theFhirContext Context holding resource definition 299 * @param theFrom The resource to merge the fields from 300 * @param theTo The resource to merge the fields into 301 * @param theFieldNameInclusion Inclusion strategy that checks if a given field should be replaced 302 */ 303 public static void replaceFields( 304 FhirContext theFhirContext, 305 IBaseResource theFrom, 306 IBaseResource theTo, 307 Predicate<String> theFieldNameInclusion) { 308 Predicate<Triple<BaseRuntimeChildDefinition, IBase, IBase>> predicate = 309 (t) -> theFieldNameInclusion.test(t.getLeft().getElementName()); 310 replaceFieldsByPredicate(theFhirContext, theFrom, theTo, predicate); 311 } 312 313 /** 314 * Replaces fields on theTo resource that test positive by the given predicate. <code>theTo</code> will contain a copy of the 315 * values from <code>theFrom</code> for which predicate tests positive. Please note that composite fields will be replaced fully. 316 * 317 * @param theFhirContext Context holding resource definition 318 * @param theFrom The resource to merge the fields from 319 * @param theTo The resource to merge the fields into 320 * @param thePredicate Predicate that checks if a given field should be replaced 321 */ 322 public static void replaceFieldsByPredicate( 323 FhirContext theFhirContext, 324 IBaseResource theFrom, 325 IBaseResource theTo, 326 Predicate<Triple<BaseRuntimeChildDefinition, IBase, IBase>> thePredicate) { 327 RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom); 328 FhirTerser terser = theFhirContext.newTerser(); 329 for (BaseRuntimeChildDefinition childDefinition : definition.getChildrenAndExtension()) { 330 if (thePredicate.test(Triple.of(childDefinition, theFrom, theTo))) { 331 replaceField(terser, theFrom, theTo, childDefinition); 332 } 333 } 334 } 335 336 /** 337 * Checks if the field exists on the resource 338 * 339 * @param theFhirContext Context holding resource definition 340 * @param theFieldName Name of the field to check 341 * @param theInstance Resource instance to check 342 * @return Returns true if resource definition has a child with the specified name and false otherwise 343 */ 344 public static boolean fieldExists(FhirContext theFhirContext, String theFieldName, IBaseResource theInstance) { 345 RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theInstance); 346 return definition.getChildByName(theFieldName) != null; 347 } 348 349 /** 350 * Replaces the specified fields on <code>theTo</code> resource with the value from <code>theFrom</code> resource. 351 * 352 * @param theFhirContext Context holding resource definition 353 * @param theFrom The resource to replace the field from 354 * @param theTo The resource to replace the field on 355 */ 356 public static void replaceField( 357 FhirContext theFhirContext, String theFieldName, IBaseResource theFrom, IBaseResource theTo) { 358 RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom); 359 Validate.notNull(definition); 360 replaceField( 361 theFhirContext.newTerser(), 362 theFrom, 363 theTo, 364 theFhirContext.getResourceDefinition(theFrom).getChildByName(theFieldName)); 365 } 366 367 /** 368 * Clears the specified field on the resource provided 369 * 370 * @param theFhirContext Context holding resource definition 371 * @param theResource 372 * @param theFieldName 373 */ 374 public static void clearField(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) { 375 BaseRuntimeChildDefinition childDefinition = 376 getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theResource); 377 childDefinition.getMutator().setValue(theResource, null); 378 } 379 380 /** 381 * Clears the specified field on the resource provided by the FHIRPath. If more than one value matches 382 * the FHIRPath, all values will be cleared. 383 * 384 * @param theFhirContext 385 * @param theResource 386 * @param theFhirPath 387 */ 388 public static void clearFieldByFhirPath(FhirContext theFhirContext, IBaseResource theResource, String theFhirPath) { 389 390 if (theFhirPath.contains(".")) { 391 String parentPath = theFhirPath.substring(0, theFhirPath.lastIndexOf(".")); 392 String fieldName = theFhirPath.substring(theFhirPath.lastIndexOf(".") + 1); 393 FhirTerser terser = theFhirContext.newTerser(); 394 List<IBase> parents = terser.getValues(theResource, parentPath); 395 for (IBase parent : parents) { 396 clearField(theFhirContext, fieldName, parent); 397 } 398 } else { 399 clearField(theFhirContext, theResource, theFhirPath); 400 } 401 } 402 403 /** 404 * Clears the specified field on the element provided 405 * 406 * @param theFhirContext Context holding resource definition 407 * @param theFieldName Name of the field to clear values for 408 * @param theBase The element definition to clear values on 409 */ 410 public static void clearField(FhirContext theFhirContext, String theFieldName, IBase theBase) { 411 BaseRuntimeElementDefinition definition = theFhirContext.getElementDefinition(theBase.getClass()); 412 BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theFieldName); 413 Validate.notNull(childDefinition); 414 BaseRuntimeChildDefinition.IAccessor accessor = childDefinition.getAccessor(); 415 clear(accessor.getValues(theBase)); 416 List<IBase> newValue = accessor.getValues(theBase); 417 418 if (newValue != null && !newValue.isEmpty()) { 419 // Our clear failed, probably because it was an immutable SingletonList returned by a FieldPlainAccessor 420 // that cannot be cleared. 421 // Let's just null it out instead. 422 childDefinition.getMutator().setValue(theBase, null); 423 } 424 } 425 426 /** 427 * Sets the provided field with the given values. This method will add to the collection of existing field values 428 * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)} 429 * to remove values before setting 430 * 431 * @param theFhirContext Context holding resource definition 432 * @param theFieldName Child field name of the resource to set 433 * @param theResource The resource to set the values on 434 * @param theValues The values to set on the resource child field name 435 */ 436 public static void setField( 437 FhirContext theFhirContext, String theFieldName, IBaseResource theResource, IBase... theValues) { 438 setField(theFhirContext, theFhirContext.newTerser(), theFieldName, theResource, theValues); 439 } 440 441 /** 442 * Sets the provided field with the given values. This method will add to the collection of existing field values 443 * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)} 444 * to remove values before setting 445 * 446 * @param theFhirContext Context holding resource definition 447 * @param theTerser Terser to be used when cloning field values 448 * @param theFieldName Child field name of the resource to set 449 * @param theResource The resource to set the values on 450 * @param theValues The values to set on the resource child field name 451 */ 452 public static void setField( 453 FhirContext theFhirContext, 454 FhirTerser theTerser, 455 String theFieldName, 456 IBaseResource theResource, 457 IBase... theValues) { 458 BaseRuntimeChildDefinition childDefinition = 459 getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theResource); 460 List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theResource); 461 if (theFromFieldValues.isEmpty()) { 462 for (IBase value : theValues) { 463 try { 464 childDefinition.getMutator().addValue(theResource, value); 465 } catch (UnsupportedOperationException e) { 466 ourLog.warn( 467 "Resource {} does not support multiple values, but an attempt to set {} was made. Setting the first item only", 468 theResource, 469 theValues); 470 childDefinition.getMutator().setValue(theResource, value); 471 break; 472 } 473 } 474 return; 475 } 476 List<IBase> theToFieldValues = Arrays.asList(theValues); 477 mergeFields(theTerser, theResource, childDefinition, theFromFieldValues, theToFieldValues); 478 } 479 480 /** 481 * Sets the provided field with the given values. This method will add to the collection of existing field values 482 * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)} 483 * to remove values before setting 484 * 485 * @param theFhirContext Context holding resource definition 486 * @param theFieldName Child field name of the resource to set 487 * @param theResource The resource to set the values on 488 * @param theValue The String value to set on the resource child field name. This value is converted to the appropriate primitive type before the value is set 489 */ 490 public static void setStringField( 491 FhirContext theFhirContext, String theFieldName, IBaseResource theResource, String theValue) { 492 setField(theFhirContext, theFieldName, theResource, theFhirContext.newPrimitiveString(theValue)); 493 } 494 495 /** 496 * Sets the specified value at the FHIR path provided. 497 * 498 * @param theTerser The terser that should be used for cloning the field value. 499 * @param theFhirPath The FHIR path to set the field at 500 * @param theResource The resource on which the value should be set 501 * @param theValue The value to set 502 */ 503 public static void setFieldByFhirPath( 504 FhirTerser theTerser, String theFhirPath, IBaseResource theResource, IBase theValue) { 505 List<IBase> theFromFieldValues = theTerser.getValues(theResource, theFhirPath, true, false); 506 for (IBase theFromFieldValue : theFromFieldValues) { 507 theTerser.cloneInto(theValue, theFromFieldValue, true); 508 } 509 } 510 511 /** 512 * Sets the specified value at the FHIR path provided. 513 * 514 * @param theFhirContext Context holding resource definition 515 * @param theFhirPath The FHIR path to set the field at 516 * @param theResource The resource on which the value should be set 517 * @param theValue The value to set 518 */ 519 public static void setFieldByFhirPath( 520 FhirContext theFhirContext, String theFhirPath, IBaseResource theResource, IBase theValue) { 521 setFieldByFhirPath(theFhirContext.newTerser(), theFhirPath, theResource, theValue); 522 } 523 524 /** 525 * Sets the specified String value at the FHIR path provided. 526 * 527 * @param theFhirContext Context holding resource definition 528 * @param theFhirPath The FHIR path to set the field at 529 * @param theResource The resource on which the value should be set 530 * @param theValue The String value to set. The string is converted to the appropriate primitive type before setting the field 531 */ 532 public static void setStringFieldByFhirPath( 533 FhirContext theFhirContext, String theFhirPath, IBaseResource theResource, String theValue) { 534 setFieldByFhirPath( 535 theFhirContext.newTerser(), theFhirPath, theResource, theFhirContext.newPrimitiveString(theValue)); 536 } 537 538 /** 539 * Returns field values ant the specified FHIR path from the resource. 540 * 541 * @param theFhirContext Context holding resource definition 542 * @param theFhirPath The FHIR path to get the field from 543 * @param theResource The resource from which the value should be retrieved 544 * @return Returns the list of field values at the given FHIR path 545 */ 546 public static List<IBase> getFieldByFhirPath(FhirContext theFhirContext, String theFhirPath, IBase theResource) { 547 return theFhirContext.newTerser().getValues(theResource, theFhirPath, false, false); 548 } 549 550 /** 551 * Returns the first available field value at the specified FHIR path from the resource. 552 * 553 * @param theFhirContext Context holding resource definition 554 * @param theFhirPath The FHIR path to get the field from 555 * @param theResource The resource from which the value should be retrieved 556 * @return Returns the first available value or null if no values can be retrieved 557 */ 558 public static IBase getFirstFieldByFhirPath(FhirContext theFhirContext, String theFhirPath, IBase theResource) { 559 List<IBase> values = getFieldByFhirPath(theFhirContext, theFhirPath, theResource); 560 if (values == null || values.isEmpty()) { 561 return null; 562 } 563 return values.get(0); 564 } 565 566 private static void replaceField( 567 FhirTerser theTerser, 568 IBaseResource theFrom, 569 IBaseResource theTo, 570 BaseRuntimeChildDefinition childDefinition) { 571 List<IBase> fromValues = childDefinition.getAccessor().getValues(theFrom); 572 List<IBase> toValues = childDefinition.getAccessor().getValues(theTo); 573 574 if (fromValues.isEmpty() && !toValues.isEmpty()) { 575 childDefinition.getMutator().setValue(theTo, null); 576 } else if (fromValues != toValues) { 577 clear(toValues); 578 579 mergeFields(theTerser, theTo, childDefinition, fromValues, toValues); 580 } 581 } 582 583 /** 584 * Merges values of all fields except for "identifier" and "meta" from <code>theFrom</code> resource to 585 * <code>theTo</code> resource. Fields values are compared via the equalsDeep method, or via object identity if this 586 * method is not available. 587 * 588 * @param theFhirContext Context holding resource definition 589 * @param theFrom Resource to merge the specified field from 590 * @param theTo Resource to merge the specified field into 591 */ 592 public static void mergeFieldsExceptIdAndMeta( 593 FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo) { 594 mergeFields(theFhirContext, theFrom, theTo, EXCLUDE_IDS_AND_META); 595 } 596 597 /** 598 * Merges values of all field from <code>theFrom</code> resource to <code>theTo</code> resource. Fields 599 * values are compared via the equalsDeep method, or via object identity if this method is not available. 600 * 601 * @param theFhirContext Context holding resource definition 602 * @param theFrom Resource to merge the specified field from 603 * @param theTo Resource to merge the specified field into 604 * @param inclusionStrategy Predicate to test which fields should be merged 605 */ 606 public static void mergeFields( 607 FhirContext theFhirContext, 608 IBaseResource theFrom, 609 IBaseResource theTo, 610 Predicate<String> inclusionStrategy) { 611 FhirTerser terser = theFhirContext.newTerser(); 612 613 RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom); 614 for (BaseRuntimeChildDefinition childDefinition : definition.getChildrenAndExtension()) { 615 if (!inclusionStrategy.test(childDefinition.getElementName())) { 616 continue; 617 } 618 619 List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theFrom); 620 List<IBase> theToFieldValues = childDefinition.getAccessor().getValues(theTo); 621 622 mergeFields(terser, theTo, childDefinition, theFromFieldValues, theToFieldValues); 623 } 624 } 625 626 /** 627 * Merges value of the specified field from <code>theFrom</code> resource to <code>theTo</code> resource. Fields 628 * values are compared via the equalsDeep method, or via object identity if this method is not available. 629 * 630 * @param theFhirContext Context holding resource definition 631 * @param theFieldName Name of the child filed to merge 632 * @param theFrom Resource to merge the specified field from 633 * @param theTo Resource to merge the specified field into 634 */ 635 public static void mergeField( 636 FhirContext theFhirContext, String theFieldName, IBaseResource theFrom, IBaseResource theTo) { 637 mergeField(theFhirContext, theFhirContext.newTerser(), theFieldName, theFrom, theTo); 638 } 639 640 /** 641 * Merges value of the specified field from <code>theFrom</code> resource to <code>theTo</code> resource. Fields 642 * values are compared via the equalsDeep method, or via object identity if this method is not available. 643 * 644 * @param theFhirContext Context holding resource definition 645 * @param theTerser Terser to be used when cloning the field values 646 * @param theFieldName Name of the child filed to merge 647 * @param theFrom Resource to merge the specified field from 648 * @param theTo Resource to merge the specified field into 649 */ 650 public static void mergeField( 651 FhirContext theFhirContext, 652 FhirTerser theTerser, 653 String theFieldName, 654 IBaseResource theFrom, 655 IBaseResource theTo) { 656 BaseRuntimeChildDefinition childDefinition = 657 getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theFrom); 658 659 List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theFrom); 660 List<IBase> theToFieldValues = childDefinition.getAccessor().getValues(theTo); 661 662 mergeFields(theTerser, theTo, childDefinition, theFromFieldValues, theToFieldValues); 663 } 664 665 private static BaseRuntimeChildDefinition getBaseRuntimeChildDefinition( 666 FhirContext theFhirContext, String theFieldName, IBaseResource theFrom) { 667 RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom); 668 BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theFieldName); 669 Validate.notNull(childDefinition); 670 return childDefinition; 671 } 672 673 /** 674 * Creates a new element taking into consideration elements with choice that are not directly retrievable by element 675 * name 676 * 677 * @param theFhirTerser 678 * @param theChildDefinition Child to create a new instance for 679 * @param theFromFieldValue The base parent field 680 * @param theConstructorParam Optional constructor param 681 * @return Returns the new element with the given value if configured 682 */ 683 private static IBase newElement( 684 FhirTerser theFhirTerser, 685 BaseRuntimeChildDefinition theChildDefinition, 686 IBase theFromFieldValue, 687 Object theConstructorParam) { 688 BaseRuntimeElementDefinition runtimeElementDefinition; 689 if (theChildDefinition instanceof RuntimeChildChoiceDefinition) { 690 runtimeElementDefinition = 691 theChildDefinition.getChildElementDefinitionByDatatype(theFromFieldValue.getClass()); 692 } else { 693 runtimeElementDefinition = theChildDefinition.getChildByName(theChildDefinition.getElementName()); 694 } 695 if ("contained".equals(runtimeElementDefinition.getName())) { 696 IBaseResource sourceResource = (IBaseResource) theFromFieldValue; 697 return theFhirTerser.clone(sourceResource); 698 } else if (theConstructorParam == null) { 699 return runtimeElementDefinition.newInstance(); 700 } else { 701 return runtimeElementDefinition.newInstance(theConstructorParam); 702 } 703 } 704 705 private static void mergeFields( 706 FhirTerser theTerser, 707 IBaseResource theTo, 708 BaseRuntimeChildDefinition childDefinition, 709 List<IBase> theFromFieldValues, 710 List<IBase> theToFieldValues) { 711 if (!theFromFieldValues.isEmpty() && theToFieldValues.stream().anyMatch(TerserUtil::hasDataAbsentReason)) { 712 // If the to resource has a data absent reason, and there is potentially real data incoming 713 // in the from resource, we should clear the data absent reason because it won't be absent anymore. 714 theToFieldValues = removeDataAbsentReason(theTo, childDefinition, theToFieldValues); 715 } 716 717 for (IBase fromFieldValue : theFromFieldValues) { 718 if (contains(fromFieldValue, theToFieldValues)) { 719 continue; 720 } 721 722 if (hasDataAbsentReason(fromFieldValue) && !theToFieldValues.isEmpty()) { 723 // if the from field value asserts a reason the field isn't populated, but the to field is populated, 724 // we don't want to overwrite real data with the extension 725 continue; 726 } 727 728 IBase newFieldValue = newElement(theTerser, childDefinition, fromFieldValue, null); 729 if (fromFieldValue instanceof IPrimitiveType) { 730 try { 731 Method copyMethod = getMethod(fromFieldValue, "copy"); 732 if (copyMethod != null) { 733 newFieldValue = (IBase) copyMethod.invoke(fromFieldValue, new Object[] {}); 734 } 735 } catch (Throwable t) { 736 ((IPrimitiveType<?>) newFieldValue) 737 .setValueAsString(((IPrimitiveType<?>) fromFieldValue).getValueAsString()); 738 } 739 } else { 740 theTerser.cloneInto(fromFieldValue, newFieldValue, true); 741 } 742 743 try { 744 theToFieldValues.add(newFieldValue); 745 } catch (UnsupportedOperationException e) { 746 childDefinition.getMutator().setValue(theTo, newFieldValue); 747 theToFieldValues = childDefinition.getAccessor().getValues(theTo); 748 } 749 } 750 } 751 752 private static List<IBase> removeDataAbsentReason( 753 IBaseResource theResource, BaseRuntimeChildDefinition theFieldDefinition, List<IBase> theFieldValues) { 754 for (int i = 0; i < theFieldValues.size(); i++) { 755 if (hasDataAbsentReason(theFieldValues.get(i))) { 756 try { 757 theFieldDefinition.getMutator().remove(theResource, i); 758 } catch (UnsupportedOperationException e) { 759 // the field must be single-valued, just clear it 760 theFieldDefinition.getMutator().setValue(theResource, null); 761 } 762 } 763 } 764 return theFieldDefinition.getAccessor().getValues(theResource); 765 } 766 767 /** 768 * Clones the specified resource. 769 * 770 * @param theFhirContext Context holding resource definition 771 * @param theInstance The instance to be cloned 772 * @param <T> Base resource type 773 * @return Returns a cloned instance 774 */ 775 public static <T extends IBaseResource> T clone(FhirContext theFhirContext, T theInstance) { 776 RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theInstance.getClass()); 777 T retVal = (T) definition.newInstance(); 778 779 FhirTerser terser = theFhirContext.newTerser(); 780 terser.cloneInto(theInstance, retVal, true); 781 return retVal; 782 } 783 784 /** 785 * Creates a new element instance 786 * 787 * @param theFhirContext Context holding resource definition 788 * @param theElementType Element type name 789 * @param <T> Base element type 790 * @return Returns a new instance of the element 791 */ 792 public static <T extends IBase> T newElement(FhirContext theFhirContext, String theElementType) { 793 BaseRuntimeElementDefinition def = theFhirContext.getElementDefinition(theElementType); 794 return (T) def.newInstance(); 795 } 796 797 /** 798 * Creates a new element instance 799 * 800 * @param theFhirContext Context holding resource definition 801 * @param theElementType Element type name 802 * @param theConstructorParam Initialization parameter for the element 803 * @param <T> Base element type 804 * @return Returns a new instance of the element with the specified initial value 805 */ 806 public static <T extends IBase> T newElement( 807 FhirContext theFhirContext, String theElementType, Object theConstructorParam) { 808 BaseRuntimeElementDefinition def = theFhirContext.getElementDefinition(theElementType); 809 Validate.notNull(def); 810 return (T) def.newInstance(theConstructorParam); 811 } 812 813 /** 814 * Creates a new resource definition. 815 * 816 * @param theFhirContext Context holding resource definition 817 * @param theResourceName Name of the resource in the context 818 * @param <T> Type of the resource 819 * @return Returns a new instance of the resource 820 */ 821 public static <T extends IBase> T newResource(FhirContext theFhirContext, String theResourceName) { 822 RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResourceName); 823 return (T) def.newInstance(); 824 } 825 826 /** 827 * Creates a new resource definition. 828 * 829 * @param theFhirContext Context holding resource definition 830 * @param theResourceName Name of the resource in the context 831 * @param theConstructorParam Initialization parameter for the new instance 832 * @param <T> Type of the resource 833 * @return Returns a new instance of the resource 834 */ 835 public static <T extends IBase> T newResource( 836 FhirContext theFhirContext, String theResourceName, Object theConstructorParam) { 837 RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResourceName); 838 return (T) def.newInstance(theConstructorParam); 839 } 840 841 /** 842 * Creates a new BackboneElement. 843 * 844 * @param theFhirContext Context holding resource definition 845 * @param theTargetResourceName Name of the resource in the context 846 * @param theTargetFieldName Name of the backbone element in the resource 847 * @return Returns a new instance of the element 848 */ 849 public static IBaseBackboneElement instantiateBackboneElement( 850 FhirContext theFhirContext, String theTargetResourceName, String theTargetFieldName) { 851 BaseRuntimeElementDefinition<?> targetParentElementDefinition = 852 theFhirContext.getResourceDefinition(theTargetResourceName); 853 BaseRuntimeChildDefinition childDefinition = targetParentElementDefinition.getChildByName(theTargetFieldName); 854 return (IBaseBackboneElement) 855 childDefinition.getChildByName(theTargetFieldName).newInstance(); 856 } 857 858 private static void clear(List<IBase> values) { 859 if (values == null) { 860 return; 861 } 862 863 try { 864 values.clear(); 865 } catch (Throwable t) { 866 ourLog.debug("Unable to clear values " + String.valueOf(values), t); 867 } 868 } 869}