diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TerserUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TerserUtil.java index 126b2dfca3b..e6de9a87c0d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TerserUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TerserUtil.java @@ -49,9 +49,15 @@ public final class TerserUtil { private static final String EQUALS_DEEP = "equalsDeep"; + /** + * Exclude for id, identifier and meta fields of a resource. + */ public static final Collection IDS_AND_META_EXCLUDES = Collections.unmodifiableSet(Stream.of("id", "identifier", "meta").collect(Collectors.toSet())); + /** + * Exclusion predicate for id, identifier, meta fields. + */ public static final Predicate EXCLUDE_IDS_AND_META = new Predicate() { @Override public boolean test(String s) { @@ -59,17 +65,25 @@ public final class TerserUtil { } }; + /** + * Exclusion predicate for id/identifier, meta and fields with empty values. This ensures that source / target resources, + * empty source fields will not results in erasure of target fields. + */ public static final Predicate> EXCLUDE_IDS_META_AND_EMPTY = new Predicate>() { @Override public boolean test(Triple theTriple) { if (!EXCLUDE_IDS_AND_META.test(theTriple.getLeft().getElementName())) { return false; } - - return theTriple.getLeft().getAccessor().getValues(theTriple.getRight()).isEmpty(); + BaseRuntimeChildDefinition childDefinition = theTriple.getLeft(); + boolean isSourceFieldEmpty = childDefinition.getAccessor().getValues(theTriple.getMiddle()).isEmpty(); + return !isSourceFieldEmpty; } }; + /** + * Exclusion predicate for keeping all fields. + */ public static final Predicate INCLUDE_ALL = new Predicate() { @Override public boolean test(String s) { @@ -262,8 +276,8 @@ public final class TerserUtil { } /** - * Replaces empty fields on theTo resource that test positive by the given predicate. theTo will contain a copy of the - * values from theFrom for which predicate tests positive. + * Replaces fields on theTo resource that test positive by the given predicate. theTo will contain a copy of the + * values from theFrom for which predicate tests positive. Please note that composite fields will be replaced fully. * * @param theFhirContext Context holding resource definition * @param theFrom The resource to merge the fields from @@ -271,15 +285,11 @@ public final class TerserUtil { * @param thePredicate Predicate that checks if a given field should be replaced */ public static void replaceFieldsByPredicate(FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo, Predicate> thePredicate) { - FhirTerser terser = theFhirContext.newTerser(); - RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom); for (BaseRuntimeChildDefinition childDefinition : definition.getChildrenAndExtension()) { - if (!thePredicate.test(Triple.of(childDefinition, theFrom, theTo))) { - continue; + if (thePredicate.test(Triple.of(childDefinition, theFrom, theTo))) { + replaceField(theFrom, theTo, childDefinition); } - - replaceField(theFrom, theTo, childDefinition); } } @@ -304,14 +314,11 @@ public final class TerserUtil { * @param theTo The resource to replace the field on */ public static void replaceField(FhirContext theFhirContext, String theFieldName, IBaseResource theFrom, IBaseResource theTo) { - replaceField(theFhirContext, theFhirContext.newTerser(), theFieldName, theFrom, theTo); - } - - /** - * @deprecated Use {@link #replaceField(FhirContext, String, IBaseResource, IBaseResource)} instead - */ - public static void replaceField(FhirContext theFhirContext, FhirTerser theTerser, String theFieldName, IBaseResource theFrom, IBaseResource theTo) { - replaceField(theFrom, theTo, getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theFrom)); + RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom); + if (definition == null) { + throw new IllegalArgumentException(String.format("Field %s does not exist in %s", theFieldName, theFrom)); + } + replaceField(theFrom, theTo, theFhirContext.getResourceDefinition(theFrom).getChildByName(theFieldName)); } /** @@ -328,7 +335,7 @@ public final class TerserUtil { /** * Sets the provided field with the given values. This method will add to the collection of existing field values - * in case of multiple cardinality. Use {@link #clearField(FhirContext, FhirTerser, String, IBaseResource, IBase...)} + * in case of multiple cardinality. Use {@link #clearField(FhirContext, String, IBaseResource)} * to remove values before setting * * @param theFhirContext Context holding resource definition @@ -342,7 +349,7 @@ public final class TerserUtil { /** * Sets the provided field with the given values. This method will add to the collection of existing field values - * in case of multiple cardinality. Use {@link #clearField(FhirContext, FhirTerser, String, IBaseResource, IBase...)} + * in case of multiple cardinality. Use {@link #clearField(FhirContext, String, IBaseResource)} * to remove values before setting * * @param theFhirContext Context holding resource definition @@ -397,10 +404,26 @@ public final class TerserUtil { setFieldByFhirPath(theFhirContext.newTerser(), theFhirPath, theResource, theValue); } + /** + * Returns field values ant the specified FHIR path from the resource. + * + * @param theFhirContext Context holding resource definition + * @param theFhirPath The FHIR path to get the field from + * @param theResource The resource from which the value should be retrieved + * @return Returns the list of field values at the given FHIR path + */ public static List getFieldByFhirPath(FhirContext theFhirContext, String theFhirPath, IBase theResource) { return theFhirContext.newTerser().getValues(theResource, theFhirPath, false, false); } + /** + * Returns the first available field value at the specified FHIR path from the resource. + * + * @param theFhirContext Context holding resource definition + * @param theFhirPath The FHIR path to get the field from + * @param theResource The resource from which the value should be retrieved + * @return Returns the first available value or null if no values can be retrieved + */ public static IBase getFirstFieldByFhirPath(FhirContext theFhirContext, String theFhirPath, IBase theResource) { List values = getFieldByFhirPath(theFhirContext, theFhirPath, theResource); if (values == null || values.isEmpty()) { diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2515-mdm-survivorship-rules-application.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2515-mdm-survivorship-rules-application.yaml index aa93351b4b4..f8e96a9f02e 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2515-mdm-survivorship-rules-application.yaml +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2515-mdm-survivorship-rules-application.yaml @@ -1,4 +1,4 @@ --- type: fix issue: 2515 -title: "Addresses MDM survivorship rules application on matching a single resource" +title: "Fixed issues with application of survivorship rules when matching golden record to a single resource" diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/TerserUtilTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/TerserUtilTest.java index a94efbfb845..0d9d03e6f32 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/TerserUtilTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/TerserUtilTest.java @@ -13,6 +13,8 @@ import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.PrimitiveType; import org.junit.jupiter.api.Test; +import java.util.Date; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.junit.jupiter.api.Assertions.*; @@ -303,12 +305,16 @@ class TerserUtilTest { Patient p2 = new Patient(); p2.addName().setFamily("Smith"); + Date dob = new Date(); + p2.setBirthDate(dob); TerserUtil.replaceFieldsByPredicate(ourFhirContext, p1, p2, TerserUtil.EXCLUDE_IDS_META_AND_EMPTY); + // expect p2 to have "Doe" and MALE after replace assertEquals(1, p2.getName().size()); - assertEquals("Smith", p2.getName().get(0).getFamily()); + assertEquals("Doe", p2.getName().get(0).getFamily()); assertEquals(Enumerations.AdministrativeGender.MALE, p2.getGender()); + assertEquals(dob, p2.getBirthDate()); } @Test