
001/*- 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2025 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 * @deprecated Use {@link ResourceUtil#mergeAllFields(FhirContext, IBase, IBase)} 290 */ 291 @Deprecated(since = "8.7.0") 292 public static void mergeAllFields(FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo) { 293 mergeFields(theFhirContext, theFrom, theTo, INCLUDE_ALL); 294 } 295 296 /** 297 * Replaces all fields that have matching field names by the given inclusion strategy. <code>theTo</code> will contain a copy of the 298 * values from <code>theFrom</code> instance. 299 * 300 * @param theFhirContext Context holding resource definition 301 * @param theFrom The resource to merge the fields from 302 * @param theTo The resource to merge the fields into 303 * @param theFieldNameInclusion Inclusion strategy that checks if a given field should be replaced 304 */ 305 public static void replaceFields( 306 FhirContext theFhirContext, 307 IBaseResource theFrom, 308 IBaseResource theTo, 309 Predicate<String> theFieldNameInclusion) { 310 Predicate<Triple<BaseRuntimeChildDefinition, IBase, IBase>> predicate = 311 (t) -> theFieldNameInclusion.test(t.getLeft().getElementName()); 312 replaceFieldsByPredicate(theFhirContext, theFrom, theTo, predicate); 313 } 314 315 /** 316 * Replaces fields on theTo resource that test positive by the given predicate. <code>theTo</code> will contain a copy of the 317 * values from <code>theFrom</code> for which predicate tests positive. Please note that composite fields will be replaced fully. 318 * 319 * @param theFhirContext Context holding resource definition 320 * @param theFrom The resource to merge the fields from 321 * @param theTo The resource to merge the fields into 322 * @param thePredicate Predicate that checks if a given field should be replaced 323 */ 324 public static void replaceFieldsByPredicate( 325 FhirContext theFhirContext, 326 IBaseResource theFrom, 327 IBaseResource theTo, 328 Predicate<Triple<BaseRuntimeChildDefinition, IBase, IBase>> thePredicate) { 329 RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom); 330 FhirTerser terser = theFhirContext.newTerser(); 331 for (BaseRuntimeChildDefinition childDefinition : definition.getChildrenAndExtension()) { 332 if (thePredicate.test(Triple.of(childDefinition, theFrom, theTo))) { 333 replaceField(terser, theFrom, theTo, childDefinition); 334 } 335 } 336 } 337 338 /** 339 * Checks if the field exists on the resource 340 * 341 * @param theFhirContext Context holding resource definition 342 * @param theFieldName Name of the field to check 343 * @param theInstance Resource instance to check 344 * @return Returns true if resource definition has a child with the specified name and false otherwise 345 */ 346 public static boolean fieldExists(FhirContext theFhirContext, String theFieldName, IBaseResource theInstance) { 347 RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theInstance); 348 return definition.getChildByName(theFieldName) != null; 349 } 350 351 /** 352 * Replaces the specified fields on <code>theTo</code> resource with the value from <code>theFrom</code> resource. 353 * 354 * @param theFhirContext Context holding resource definition 355 * @param theFrom The resource to replace the field from 356 * @param theTo The resource to replace the field on 357 */ 358 public static void replaceField( 359 FhirContext theFhirContext, String theFieldName, IBaseResource theFrom, IBaseResource theTo) { 360 RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom); 361 Validate.notNull(definition); 362 replaceField( 363 theFhirContext.newTerser(), 364 theFrom, 365 theTo, 366 theFhirContext.getResourceDefinition(theFrom).getChildByName(theFieldName)); 367 } 368 369 /** 370 * Clears the specified field on the resource provided 371 * 372 * @param theFhirContext Context holding resource definition 373 * @param theResource 374 * @param theFieldName 375 */ 376 public static void clearField(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) { 377 BaseRuntimeChildDefinition childDefinition = 378 getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theResource); 379 childDefinition.getMutator().setValue(theResource, null); 380 } 381 382 /** 383 * Clears the specified field on the resource provided by the FHIRPath. If more than one value matches 384 * the FHIRPath, all values will be cleared. 385 * 386 * @param theFhirContext 387 * @param theResource 388 * @param theFhirPath 389 */ 390 public static void clearFieldByFhirPath(FhirContext theFhirContext, IBaseResource theResource, String theFhirPath) { 391 392 if (theFhirPath.contains(".")) { 393 String parentPath = theFhirPath.substring(0, theFhirPath.lastIndexOf(".")); 394 String fieldName = theFhirPath.substring(theFhirPath.lastIndexOf(".") + 1); 395 FhirTerser terser = theFhirContext.newTerser(); 396 List<IBase> parents = terser.getValues(theResource, parentPath); 397 for (IBase parent : parents) { 398 clearField(theFhirContext, fieldName, parent); 399 } 400 } else { 401 clearField(theFhirContext, theResource, theFhirPath); 402 } 403 } 404 405 /** 406 * Clears the specified field on the element provided 407 * 408 * @param theFhirContext Context holding resource definition 409 * @param theFieldName Name of the field to clear values for 410 * @param theBase The element definition to clear values on 411 */ 412 public static void clearField(FhirContext theFhirContext, String theFieldName, IBase theBase) { 413 BaseRuntimeElementDefinition definition = theFhirContext.getElementDefinition(theBase.getClass()); 414 BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theFieldName); 415 Validate.notNull(childDefinition); 416 BaseRuntimeChildDefinition.IAccessor accessor = childDefinition.getAccessor(); 417 clear(accessor.getValues(theBase)); 418 List<IBase> newValue = accessor.getValues(theBase); 419 420 if (newValue != null && !newValue.isEmpty()) { 421 // Our clear failed, probably because it was an immutable SingletonList returned by a FieldPlainAccessor 422 // that cannot be cleared. 423 // Let's just null it out instead. 424 childDefinition.getMutator().setValue(theBase, null); 425 } 426 } 427 428 /** 429 * Sets the provided field with the given values. This method will add to the collection of existing field values 430 * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)} 431 * to remove values before setting 432 * 433 * @param theFhirContext Context holding resource definition 434 * @param theFieldName Child field name of the resource to set 435 * @param theResource The resource to set the values on 436 * @param theValues The values to set on the resource child field name 437 */ 438 public static void setField( 439 FhirContext theFhirContext, String theFieldName, IBaseResource theResource, IBase... theValues) { 440 setField(theFhirContext, theFhirContext.newTerser(), theFieldName, theResource, theValues); 441 } 442 443 /** 444 * Sets the provided field with the given values. This method will add to the collection of existing field values 445 * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)} 446 * to remove values before setting 447 * 448 * @param theFhirContext Context holding resource definition 449 * @param theTerser Terser to be used when cloning field values 450 * @param theFieldName Child field name of the resource to set 451 * @param theResource The resource to set the values on 452 * @param theValues The values to set on the resource child field name 453 */ 454 public static void setField( 455 FhirContext theFhirContext, 456 FhirTerser theTerser, 457 String theFieldName, 458 IBaseResource theResource, 459 IBase... theValues) { 460 BaseRuntimeChildDefinition childDefinition = 461 getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theResource); 462 List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theResource); 463 if (theFromFieldValues.isEmpty()) { 464 for (IBase value : theValues) { 465 try { 466 childDefinition.getMutator().addValue(theResource, value); 467 } catch (UnsupportedOperationException e) { 468 ourLog.warn( 469 "Resource {} does not support multiple values, but an attempt to set {} was made. Setting the first item only", 470 theResource, 471 theValues); 472 childDefinition.getMutator().setValue(theResource, value); 473 break; 474 } 475 } 476 return; 477 } 478 List<IBase> theToFieldValues = Arrays.asList(theValues); 479 mergeFields(theTerser, theResource, childDefinition, theFromFieldValues, theToFieldValues); 480 } 481 482 /** 483 * Sets the provided field with the given values. This method will add to the collection of existing field values 484 * in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)} 485 * to remove values before setting 486 * 487 * @param theFhirContext Context holding resource definition 488 * @param theFieldName Child field name of the resource to set 489 * @param theResource The resource to set the values on 490 * @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 491 */ 492 public static void setStringField( 493 FhirContext theFhirContext, String theFieldName, IBaseResource theResource, String theValue) { 494 setField(theFhirContext, theFieldName, theResource, theFhirContext.newPrimitiveString(theValue)); 495 } 496 497 /** 498 * Sets the specified value at the FHIR path provided. 499 * 500 * @param theTerser The terser that should be used for cloning the field value. 501 * @param theFhirPath The FHIR path to set the field at 502 * @param theResource The resource on which the value should be set 503 * @param theValue The value to set 504 */ 505 public static void setFieldByFhirPath( 506 FhirTerser theTerser, String theFhirPath, IBaseResource theResource, IBase theValue) { 507 List<IBase> theFromFieldValues = theTerser.getValues(theResource, theFhirPath, true, false); 508 for (IBase theFromFieldValue : theFromFieldValues) { 509 theTerser.cloneInto(theValue, theFromFieldValue, true); 510 } 511 } 512 513 /** 514 * Sets the specified value at the FHIR path provided. 515 * 516 * @param theFhirContext Context holding resource definition 517 * @param theFhirPath The FHIR path to set the field at 518 * @param theResource The resource on which the value should be set 519 * @param theValue The value to set 520 */ 521 public static void setFieldByFhirPath( 522 FhirContext theFhirContext, String theFhirPath, IBaseResource theResource, IBase theValue) { 523 setFieldByFhirPath(theFhirContext.newTerser(), theFhirPath, theResource, theValue); 524 } 525 526 /** 527 * Sets the specified String value at the FHIR path provided. 528 * 529 * @param theFhirContext Context holding resource definition 530 * @param theFhirPath The FHIR path to set the field at 531 * @param theResource The resource on which the value should be set 532 * @param theValue The String value to set. The string is converted to the appropriate primitive type before setting the field 533 */ 534 public static void setStringFieldByFhirPath( 535 FhirContext theFhirContext, String theFhirPath, IBaseResource theResource, String theValue) { 536 setFieldByFhirPath( 537 theFhirContext.newTerser(), theFhirPath, theResource, theFhirContext.newPrimitiveString(theValue)); 538 } 539 540 /** 541 * Returns field values ant the specified FHIR path from the resource. 542 * 543 * @param theFhirContext Context holding resource definition 544 * @param theFhirPath The FHIR path to get the field from 545 * @param theResource The resource from which the value should be retrieved 546 * @return Returns the list of field values at the given FHIR path 547 */ 548 public static List<IBase> getFieldByFhirPath(FhirContext theFhirContext, String theFhirPath, IBase theResource) { 549 return theFhirContext.newTerser().getValues(theResource, theFhirPath, false, false); 550 } 551 552 /** 553 * Returns the first available field value at the specified FHIR path from the resource. 554 * 555 * @param theFhirContext Context holding resource definition 556 * @param theFhirPath The FHIR path to get the field from 557 * @param theResource The resource from which the value should be retrieved 558 * @return Returns the first available value or null if no values can be retrieved 559 */ 560 public static IBase getFirstFieldByFhirPath(FhirContext theFhirContext, String theFhirPath, IBase theResource) { 561 List<IBase> values = getFieldByFhirPath(theFhirContext, theFhirPath, theResource); 562 if (values == null || values.isEmpty()) { 563 return null; 564 } 565 return values.get(0); 566 } 567 568 private static void replaceField( 569 FhirTerser theTerser, 570 IBaseResource theFrom, 571 IBaseResource theTo, 572 BaseRuntimeChildDefinition childDefinition) { 573 List<IBase> fromValues = childDefinition.getAccessor().getValues(theFrom); 574 List<IBase> toValues = childDefinition.getAccessor().getValues(theTo); 575 576 if (fromValues.isEmpty() && !toValues.isEmpty()) { 577 childDefinition.getMutator().setValue(theTo, null); 578 } else if (fromValues != toValues) { 579 clear(toValues); 580 581 mergeFields(theTerser, theTo, childDefinition, fromValues, toValues); 582 } 583 } 584 585 /** 586 * Merges values of all fields except for "identifier" and "meta" from <code>theFrom</code> resource to 587 * <code>theTo</code> resource. Fields values are compared via the equalsDeep method, or via object identity if this 588 * method is not available. 589 * 590 * @param theFhirContext Context holding resource definition 591 * @param theFrom Resource to merge the specified field from 592 * @param theTo Resource to merge the specified field into 593 */ 594 public static void mergeFieldsExceptIdAndMeta( 595 FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo) { 596 mergeFields(theFhirContext, theFrom, theTo, EXCLUDE_IDS_AND_META); 597 } 598 599 /** 600 * Merges values of all field from <code>theFrom</code> resource to <code>theTo</code> resource. Fields 601 * values are compared via the equalsDeep method, or via object identity if this method is not available. 602 * 603 * @param theFhirContext Context holding resource definition 604 * @param theFrom Resource to merge the specified field from 605 * @param theTo Resource to merge the specified field into 606 * @param inclusionStrategy Predicate to test which fields should be merged 607 * @deprecated Use {@link ResourceUtil#mergeFields(FhirContext, IBase, IBase, Predicate)} 608 */ 609 @Deprecated(since = "8.7.0") 610 public static void mergeFields( 611 FhirContext theFhirContext, 612 IBaseResource theFrom, 613 IBaseResource theTo, 614 Predicate<String> inclusionStrategy) { 615 FhirTerser terser = theFhirContext.newTerser(); 616 617 RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom); 618 for (BaseRuntimeChildDefinition childDefinition : definition.getChildrenAndExtension()) { 619 if (!inclusionStrategy.test(childDefinition.getElementName())) { 620 continue; 621 } 622 623 List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theFrom); 624 List<IBase> theToFieldValues = childDefinition.getAccessor().getValues(theTo); 625 626 mergeFields(terser, theTo, childDefinition, theFromFieldValues, theToFieldValues); 627 } 628 } 629 630 /** 631 * Merges value of the specified field from <code>theFrom</code> resource to <code>theTo</code> resource. Fields 632 * values are compared via the equalsDeep method, or via object identity if this method is not available. 633 * 634 * @param theFhirContext Context holding resource definition 635 * @param theFieldName Name of the child filed to merge 636 * @param theFrom Resource to merge the specified field from 637 * @param theTo Resource to merge the specified field into 638 * @deprecated Use {@link ResourceUtil#mergeField(FhirContext, String, IBaseResource, IBaseResource)} 639 */ 640 @Deprecated(since = "8.7.0") 641 public static void mergeField( 642 FhirContext theFhirContext, String theFieldName, IBaseResource theFrom, IBaseResource theTo) { 643 mergeField(theFhirContext, theFhirContext.newTerser(), theFieldName, theFrom, theTo); 644 } 645 646 /** 647 * Merges value of the specified field from <code>theFrom</code> resource to <code>theTo</code> resource. Fields 648 * values are compared via the equalsDeep method, or via object identity if this method is not available. 649 * 650 * @param theFhirContext Context holding resource definition 651 * @param theTerser Terser to be used when cloning the field values 652 * @param theFieldName Name of the child filed to merge 653 * @param theFrom Resource to merge the specified field from 654 * @param theTo Resource to merge the specified field into 655 * @deprecated Use {@link ResourceUtil#mergeField(FhirContext, String, IBaseResource, IBaseResource)} 656 */ 657 @Deprecated(since = "8.7.0") 658 public static void mergeField( 659 FhirContext theFhirContext, 660 FhirTerser theTerser, 661 String theFieldName, 662 IBaseResource theFrom, 663 IBaseResource theTo) { 664 BaseRuntimeChildDefinition childDefinition = 665 getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theFrom); 666 667 List<IBase> theFromFieldValues = childDefinition.getAccessor().getValues(theFrom); 668 List<IBase> theToFieldValues = childDefinition.getAccessor().getValues(theTo); 669 670 mergeFields(theTerser, theTo, childDefinition, theFromFieldValues, theToFieldValues); 671 } 672 673 private static BaseRuntimeChildDefinition getBaseRuntimeChildDefinition( 674 FhirContext theFhirContext, String theFieldName, IBaseResource theFrom) { 675 RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom); 676 BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theFieldName); 677 Validate.notNull(childDefinition); 678 return childDefinition; 679 } 680 681 /** 682 * Creates a new element taking into consideration elements with choice that are not directly retrievable by element 683 * name 684 * 685 * @param theFhirTerser 686 * @param theChildDefinition Child to create a new instance for 687 * @param theFromFieldValue The base parent field 688 * @param theConstructorParam Optional constructor param 689 * @return Returns the new element with the given value if configured 690 */ 691 private static IBase newElement( 692 FhirTerser theFhirTerser, 693 BaseRuntimeChildDefinition theChildDefinition, 694 IBase theFromFieldValue, 695 Object theConstructorParam) { 696 BaseRuntimeElementDefinition runtimeElementDefinition; 697 if (theChildDefinition instanceof RuntimeChildChoiceDefinition) { 698 runtimeElementDefinition = 699 theChildDefinition.getChildElementDefinitionByDatatype(theFromFieldValue.getClass()); 700 } else { 701 runtimeElementDefinition = theChildDefinition.getChildByName(theChildDefinition.getElementName()); 702 } 703 if ("contained".equals(runtimeElementDefinition.getName())) { 704 IBaseResource sourceResource = (IBaseResource) theFromFieldValue; 705 return theFhirTerser.clone(sourceResource); 706 } else if (theConstructorParam == null) { 707 return runtimeElementDefinition.newInstance(); 708 } else { 709 return runtimeElementDefinition.newInstance(theConstructorParam); 710 } 711 } 712 713 private static void mergeFields( 714 FhirTerser theTerser, 715 IBaseResource theTo, 716 BaseRuntimeChildDefinition childDefinition, 717 List<IBase> theFromFieldValues, 718 List<IBase> theToFieldValues) { 719 if (!theFromFieldValues.isEmpty() && theToFieldValues.stream().anyMatch(TerserUtil::hasDataAbsentReason)) { 720 // If the to resource has a data absent reason, and there is potentially real data incoming 721 // in the from resource, we should clear the data absent reason because it won't be absent anymore. 722 theToFieldValues = removeDataAbsentReason(theTo, childDefinition, theToFieldValues); 723 } 724 725 for (IBase fromFieldValue : theFromFieldValues) { 726 if (contains(fromFieldValue, theToFieldValues)) { 727 continue; 728 } 729 730 if (hasDataAbsentReason(fromFieldValue) && !theToFieldValues.isEmpty()) { 731 // if the from field value asserts a reason the field isn't populated, but the to field is populated, 732 // we don't want to overwrite real data with the extension 733 continue; 734 } 735 736 IBase newFieldValue = newElement(theTerser, childDefinition, fromFieldValue, null); 737 if (fromFieldValue instanceof IPrimitiveType) { 738 try { 739 Method copyMethod = getMethod(fromFieldValue, "copy"); 740 if (copyMethod != null) { 741 newFieldValue = (IBase) copyMethod.invoke(fromFieldValue, new Object[] {}); 742 } 743 } catch (Throwable t) { 744 ((IPrimitiveType<?>) newFieldValue) 745 .setValueAsString(((IPrimitiveType<?>) fromFieldValue).getValueAsString()); 746 } 747 } else { 748 theTerser.cloneInto(fromFieldValue, newFieldValue, true); 749 } 750 751 try { 752 theToFieldValues.add(newFieldValue); 753 } catch (UnsupportedOperationException e) { 754 childDefinition.getMutator().setValue(theTo, newFieldValue); 755 theToFieldValues = childDefinition.getAccessor().getValues(theTo); 756 } 757 } 758 } 759 760 private static List<IBase> removeDataAbsentReason( 761 IBaseResource theResource, BaseRuntimeChildDefinition theFieldDefinition, List<IBase> theFieldValues) { 762 for (int i = 0; i < theFieldValues.size(); i++) { 763 if (hasDataAbsentReason(theFieldValues.get(i))) { 764 try { 765 theFieldDefinition.getMutator().remove(theResource, i); 766 } catch (UnsupportedOperationException e) { 767 // the field must be single-valued, just clear it 768 theFieldDefinition.getMutator().setValue(theResource, null); 769 } 770 } 771 } 772 return theFieldDefinition.getAccessor().getValues(theResource); 773 } 774 775 /** 776 * Clones the specified resource. 777 * 778 * @param theFhirContext Context holding resource definition 779 * @param theInstance The instance to be cloned 780 * @param <T> Base resource type 781 * @return Returns a cloned instance 782 */ 783 public static <T extends IBaseResource> T clone(FhirContext theFhirContext, T theInstance) { 784 RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theInstance.getClass()); 785 T retVal = (T) definition.newInstance(); 786 787 FhirTerser terser = theFhirContext.newTerser(); 788 terser.cloneInto(theInstance, retVal, true); 789 return retVal; 790 } 791 792 /** 793 * Creates a new element instance 794 * 795 * @param theFhirContext Context holding resource definition 796 * @param theElementType Element type name 797 * @param <T> Base element type 798 * @return Returns a new instance of the element 799 */ 800 public static <T extends IBase> T newElement(FhirContext theFhirContext, String theElementType) { 801 BaseRuntimeElementDefinition def = theFhirContext.getElementDefinition(theElementType); 802 return (T) def.newInstance(); 803 } 804 805 /** 806 * Creates a new element instance 807 * 808 * @param theFhirContext Context holding resource definition 809 * @param theElementType Element type name 810 * @param theConstructorParam Initialization parameter for the element 811 * @param <T> Base element type 812 * @return Returns a new instance of the element with the specified initial value 813 */ 814 public static <T extends IBase> T newElement( 815 FhirContext theFhirContext, String theElementType, Object theConstructorParam) { 816 BaseRuntimeElementDefinition def = theFhirContext.getElementDefinition(theElementType); 817 Validate.notNull(def); 818 return (T) def.newInstance(theConstructorParam); 819 } 820 821 /** 822 * Creates a new resource definition. 823 * 824 * @param theFhirContext Context holding resource definition 825 * @param theResourceName Name of the resource in the context 826 * @param <T> Type of the resource 827 * @return Returns a new instance of the resource 828 */ 829 public static <T extends IBase> T newResource(FhirContext theFhirContext, String theResourceName) { 830 RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResourceName); 831 return (T) def.newInstance(); 832 } 833 834 /** 835 * Creates a new resource definition. 836 * 837 * @param theFhirContext Context holding resource definition 838 * @param theResourceName Name of the resource in the context 839 * @param theConstructorParam Initialization parameter for the new instance 840 * @param <T> Type of the resource 841 * @return Returns a new instance of the resource 842 */ 843 public static <T extends IBase> T newResource( 844 FhirContext theFhirContext, String theResourceName, Object theConstructorParam) { 845 RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResourceName); 846 return (T) def.newInstance(theConstructorParam); 847 } 848 849 /** 850 * Creates a new BackboneElement. 851 * 852 * @param theFhirContext Context holding resource definition 853 * @param theTargetResourceName Name of the resource in the context 854 * @param theTargetFieldName Name of the backbone element in the resource 855 * @return Returns a new instance of the element 856 */ 857 public static IBaseBackboneElement instantiateBackboneElement( 858 FhirContext theFhirContext, String theTargetResourceName, String theTargetFieldName) { 859 BaseRuntimeElementDefinition<?> targetParentElementDefinition = 860 theFhirContext.getResourceDefinition(theTargetResourceName); 861 BaseRuntimeChildDefinition childDefinition = targetParentElementDefinition.getChildByName(theTargetFieldName); 862 return (IBaseBackboneElement) 863 childDefinition.getChildByName(theTargetFieldName).newInstance(); 864 } 865 866 private static void clear(List<IBase> values) { 867 if (values == null) { 868 return; 869 } 870 871 try { 872 values.clear(); 873 } catch (Throwable t) { 874 ourLog.debug("Unable to clear values " + String.valueOf(values), t); 875 } 876 } 877}