diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5957-fix-mdm-class-cast-exception-when-phonetically-matching-by-humanname.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5957-fix-mdm-class-cast-exception-when-phonetically-matching-by-humanname.yaml new file mode 100644 index 00000000000..e36aad6dae1 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5957-fix-mdm-class-cast-exception-when-phonetically-matching-by-humanname.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 6077 +title: "Previously, when a MDM rule tried to perform a phonetic match by HumanName (eg. Patient.name), a +`ClassCastException` was thrown. This has now been fixed." diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/matcher/fieldmatchers/PhoneticEncoderMatcher.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/matcher/fieldmatchers/PhoneticEncoderMatcher.java index 26e91559811..7c31fbd3314 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/matcher/fieldmatchers/PhoneticEncoderMatcher.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/matcher/fieldmatchers/PhoneticEncoderMatcher.java @@ -45,9 +45,17 @@ public class PhoneticEncoderMatcher implements IMdmFieldMatcher { @Override public boolean matches(IBase theLeftBase, IBase theRightBase, MdmMatcherJson theParams) { - String leftString = StringMatcherUtils.extractString((IPrimitiveType) theLeftBase, theParams.getExact()); - String rightString = StringMatcherUtils.extractString((IPrimitiveType) theRightBase, theParams.getExact()); + if (theLeftBase instanceof IPrimitiveType && theRightBase instanceof IPrimitiveType) { + String leftString = StringMatcherUtils.extractString((IPrimitiveType) theLeftBase, theParams.getExact()); + String rightString = + StringMatcherUtils.extractString((IPrimitiveType) theRightBase, theParams.getExact()); - return matches(leftString, rightString); + return matches(leftString, rightString); + } + ourLog.warn( + "Unable to evaluate match between {} and {} because they are not an instance of PrimitiveType.", + theLeftBase.getClass().getSimpleName(), + theRightBase.getClass().getSimpleName()); + return false; } } diff --git a/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/svc/ResourceMatcherR4Test.java b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/svc/ResourceMatcherR4Test.java index 8fa816bc5f1..74ab179211d 100644 --- a/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/svc/ResourceMatcherR4Test.java +++ b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/svc/ResourceMatcherR4Test.java @@ -6,15 +6,29 @@ import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; import ca.uhn.fhir.mdm.rules.json.MdmFieldMatchJson; import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson; import ca.uhn.fhir.mdm.rules.json.MdmRulesJson; +import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.PhoneticEncoderMatcher; import ca.uhn.fhir.mdm.rules.matcher.models.MatchTypeEnum; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; import org.hl7.fhir.r4.model.HumanName; import org.hl7.fhir.r4.model.Patient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.slf4j.LoggerFactory; import java.util.Arrays; +import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; public class ResourceMatcherR4Test extends BaseMdmRulesR4Test { @@ -23,6 +37,10 @@ public class ResourceMatcherR4Test extends BaseMdmRulesR4Test { public static final String PHONE_NUMBER = "123 456789"; private Patient myLeft; private Patient myRight; + @Mock + private Appender myAppender; + @Captor + ArgumentCaptor myLoggingEvent; @Override @BeforeEach @@ -61,6 +79,41 @@ public class ResourceMatcherR4Test extends BaseMdmRulesR4Test { assertMatchResult(MdmMatchResultEnum.MATCH, 7L, 3.0, false, false, result); } + @Test + public void testMetaphoneOnName() { + Logger logger = (Logger) LoggerFactory.getLogger(PhoneticEncoderMatcher.class); + logger.addAppender(myAppender); + + // Given: MDM rules that match by Patient.name using some phonetic algorithm + MdmFieldMatchJson lastNameMatchField = new MdmFieldMatchJson() + .setName(PATIENT_FAMILY) + .setResourceType("Patient") + .setResourcePath("name") + .setMatcher(new MdmMatcherJson().setAlgorithm(MatchTypeEnum.METAPHONE)); + + MdmRulesJson retval = new MdmRulesJson(); + retval.setVersion("test version"); + retval.addMatchField(lastNameMatchField); + retval.setMdmTypes(List.of("Patient")); + retval.putMatchResult(PATIENT_FAMILY, MdmMatchResultEnum.MATCH); + + // When + MdmResourceMatcherSvc matcherSvc = buildMatcher(retval); + MdmMatchOutcome result = matcherSvc.match(myLeft, myRight); + + // Then: expect a logged message notifying that we are unable to phonetically match by HumanName + verify(myAppender, times(1)).doAppend(myLoggingEvent.capture()); + ILoggingEvent event = myLoggingEvent.getValue(); + assertEquals(Level.WARN, event.getLevel()); + assertEquals("Unable to evaluate match between HumanName and HumanName because they are not an instance of PrimitiveType.", event.getFormattedMessage()); + + logger.detachAppender(myAppender); + verifyNoMoreInteractions(myAppender); + System.clearProperty("unit_test"); + + assertMatchResult(MdmMatchResultEnum.NO_MATCH, 0L, 0.0, false, false, result); + } + @Test public void testStringMatchResult() { MdmResourceMatcherSvc matcherSvc = buildMatcher(buildNamePhoneRules(MatchTypeEnum.STRING));