From cdda12ef8e8900fb548036b3b741a9aae7f2077d Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 21 Jan 2021 17:42:28 -0500 Subject: [PATCH] Added empty matcher --- .../mdm/rules/json/MdmFieldMatchJson.java | 4 +++ .../fhir/mdm/rules/json/MdmMatcherJson.java | 4 +++ .../mdm/rules/matcher/EmptyFieldMatcher.java | 20 +++++++++++ .../mdm/rules/matcher/MdmMatcherEnum.java | 14 +++++++- .../rules/svc/MdmResourceFieldMatcher.java | 11 +++++-- .../rules/matcher/EmptyFieldMatcherTest.java | 32 ++++++++++++++++++ .../svc/MdmResourceFieldMatcherR4Test.java | 33 +++++++++++++++++++ 7 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/matcher/EmptyFieldMatcher.java create mode 100644 hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/matcher/EmptyFieldMatcherTest.java diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/json/MdmFieldMatchJson.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/json/MdmFieldMatchJson.java index 02d143cd70e..aba025c0bcd 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/json/MdmFieldMatchJson.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/json/MdmFieldMatchJson.java @@ -84,6 +84,10 @@ public class MdmFieldMatchJson implements IModelJson { return myMatcher; } + public boolean isMatcherSupportingEmptyFields() { + return (getMatcher() == null) ? false : getMatcher().isMatchingEmptyFields(); + } + public MdmFieldMatchJson setMatcher(MdmMatcherJson theMatcher) { myMatcher = theMatcher; return this; diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/json/MdmMatcherJson.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/json/MdmMatcherJson.java index 45e8efac16c..5245b6ce959 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/json/MdmMatcherJson.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/json/MdmMatcherJson.java @@ -66,6 +66,10 @@ public class MdmMatcherJson implements IModelJson { return this; } + public boolean isMatchingEmptyFields() { + return myAlgorithm.isMatchingEmptyFields(); + } + public boolean match(FhirContext theFhirContext, IBase theLeftValue, IBase theRightValue) { return myAlgorithm.match(theFhirContext, theLeftValue, theRightValue, myExact, myIdentifierSystem); } diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/matcher/EmptyFieldMatcher.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/matcher/EmptyFieldMatcher.java new file mode 100644 index 00000000000..d8bfb320076 --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/matcher/EmptyFieldMatcher.java @@ -0,0 +1,20 @@ +package ca.uhn.fhir.mdm.rules.matcher; + +import ca.uhn.fhir.context.FhirContext; +import org.hl7.fhir.instance.model.api.IBase; + +public class EmptyFieldMatcher implements IMdmFieldMatcher { + + public EmptyFieldMatcher() { + } + + @Override + public boolean matches(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, String theIdentifierSystem) { + for (IBase b : new IBase[] {theLeftBase, theRightBase}) { + if (b != null && !b.isEmpty()) { + return false; + } + } + return true; + } +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/matcher/MdmMatcherEnum.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/matcher/MdmMatcherEnum.java index 49f3f932819..e95a2d961e3 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/matcher/MdmMatcherEnum.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/matcher/MdmMatcherEnum.java @@ -48,7 +48,9 @@ public enum MdmMatcherEnum { NAME_ANY_ORDER(new NameMatcher(MdmNameMatchModeEnum.ANY_ORDER)), NAME_FIRST_AND_LAST(new NameMatcher(MdmNameMatchModeEnum.FIRST_AND_LAST)), - IDENTIFIER(new IdentifierMatcher()); + IDENTIFIER(new IdentifierMatcher()), + + EMPTY_FIELD(new EmptyFieldMatcher()); private final IMdmFieldMatcher myMdmFieldMatcher; @@ -69,4 +71,14 @@ public enum MdmMatcherEnum { public boolean match(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, String theIdentifierSystem) { return myMdmFieldMatcher.matches(theFhirContext, theLeftBase, theRightBase, theExact, theIdentifierSystem); } + + /** + * Checks if this matcher supports checks on empty fields + * + * @return + * Returns true of this matcher supports empty fields and false otherwise + */ + public boolean isMatchingEmptyFields() { + return this == EMPTY_FIELD; + } } diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/svc/MdmResourceFieldMatcher.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/svc/MdmResourceFieldMatcher.java index 83014c4ae4d..2c8807b907e 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/svc/MdmResourceFieldMatcher.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/svc/MdmResourceFieldMatcher.java @@ -58,11 +58,11 @@ public class MdmResourceFieldMatcher { /** * Compares two {@link IBaseResource}s and determines if they match, using the algorithm defined in this object's * {@link MdmFieldMatchJson}. - * + *

* In this implementation, it determines whether a given field matches between two resources. Internally this is evaluated using FhirPath. If any of the elements of theLeftResource * match any of the elements of theRightResource, will return true. Otherwise, false. * - * @param theLeftResource the first {@link IBaseResource} + * @param theLeftResource the first {@link IBaseResource} * @param theRightResource the second {@link IBaseResource} * @return A boolean indicating whether they match. */ @@ -80,12 +80,19 @@ public class MdmResourceFieldMatcher { @SuppressWarnings("rawtypes") private MdmMatchEvaluation match(List theLeftValues, List theRightValues) { MdmMatchEvaluation retval = new MdmMatchEvaluation(false, 0.0); + + boolean isMatchingEmptyFieldValues = (theLeftValues.isEmpty() && theRightValues.isEmpty()); + if (isMatchingEmptyFieldValues && myMdmFieldMatchJson.isMatcherSupportingEmptyFields()) { + return match((IBase) null, (IBase) null); + } + for (IBase leftValue : theLeftValues) { for (IBase rightValue : theRightValues) { MdmMatchEvaluation nextMatch = match(leftValue, rightValue); retval = MdmMatchEvaluation.max(retval, nextMatch); } } + return retval; } diff --git a/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/matcher/EmptyFieldMatcherTest.java b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/matcher/EmptyFieldMatcherTest.java new file mode 100644 index 00000000000..f1ef1982b22 --- /dev/null +++ b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/matcher/EmptyFieldMatcherTest.java @@ -0,0 +1,32 @@ +package ca.uhn.fhir.mdm.rules.matcher; + +import ca.uhn.fhir.mdm.rules.json.MdmFieldMatchJson; +import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson; +import ca.uhn.fhir.model.primitive.StringDt; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EmptyFieldMatcherTest extends BaseMatcherR4Test { + + @Test + public void testEmptyFieldMatch() { + StringDt left = new StringDt("other value"); + StringDt leftEmpty = new StringDt(""); + StringDt rightEmpty = new StringDt(""); + StringDt right = new StringDt("a value"); + MdmMatcherJson matcher = new MdmMatcherJson().setAlgorithm(MdmMatcherEnum.EMPTY_FIELD); + MdmFieldMatchJson fieldMatch = new MdmFieldMatchJson().setMatcher(matcher); + + assertTrue(fieldMatch.match(ourFhirContext, null, null).match); + assertTrue(fieldMatch.match(ourFhirContext, null, rightEmpty).match); + assertTrue(fieldMatch.match(ourFhirContext, leftEmpty, null).match); + assertTrue(fieldMatch.match(ourFhirContext, leftEmpty, rightEmpty).match); + assertFalse(fieldMatch.match(ourFhirContext, null, right).match); + assertFalse(fieldMatch.match(ourFhirContext, left, null).match); + assertFalse(fieldMatch.match(ourFhirContext, left, right).match); + } + + +} diff --git a/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/svc/MdmResourceFieldMatcherR4Test.java b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/svc/MdmResourceFieldMatcherR4Test.java index bfff9f04056..fea95e21926 100644 --- a/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/svc/MdmResourceFieldMatcherR4Test.java +++ b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/svc/MdmResourceFieldMatcherR4Test.java @@ -1,7 +1,10 @@ package ca.uhn.fhir.mdm.rules.svc; 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.json.MdmSimilarityJson; +import ca.uhn.fhir.mdm.rules.matcher.MdmMatcherEnum; import ca.uhn.fhir.mdm.rules.similarity.MdmSimilarityEnum; import ca.uhn.fhir.parser.DataFormatException; import org.hl7.fhir.r4.model.Encounter; @@ -9,6 +12,8 @@ import org.hl7.fhir.r4.model.Patient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.Arrays; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.StringStartsWith.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -31,6 +36,34 @@ public class MdmResourceFieldMatcherR4Test extends BaseMdmRulesR4Test { myJohny = buildJohny(); } + @Test + public void testEmptyPath() { + myMdmRulesJson = new MdmRulesJson(); + myMdmRulesJson.setMdmTypes(Arrays.asList(new String[]{"Patient"})); + + myGivenNameMatchField = new MdmFieldMatchJson() + .setName("empty-given") + .setResourceType("Patient") + .setResourcePath("name.given") + .setMatcher(new MdmMatcherJson().setAlgorithm(MdmMatcherEnum.EMPTY_FIELD)); + myComparator = new MdmResourceFieldMatcher(ourFhirContext, myGivenNameMatchField, myMdmRulesJson); + + assertFalse(myComparator.match(myJohn, myJohny).match); + + myJohn.getName().clear(); + myJohny.getName().clear(); + + assertTrue(myComparator.match(myJohn, myJohny).match); + + myJohn = buildJohn(); + myJohny.getName().clear(); + assertFalse(myComparator.match(myJohn, myJohny).match); + + myJohn.getName().clear(); + myJohny = buildJohny(); + assertFalse(myComparator.match(myJohn, myJohny).match); + } + @Test public void testSimplePatient() { Patient patient = new Patient();