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