diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/broker/EmpiMessageHandler.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/broker/EmpiMessageHandler.java index f98b563fda2..9b05e2793b5 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/broker/EmpiMessageHandler.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/broker/EmpiMessageHandler.java @@ -28,6 +28,7 @@ import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.empi.svc.EmpiMatchLinkSvc; +import ca.uhn.fhir.jpa.empi.svc.EmpiResourceFilteringSvc; import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; import ca.uhn.fhir.rest.server.TransactionLogMessages; @@ -50,6 +51,8 @@ public class EmpiMessageHandler implements MessageHandler { private IInterceptorBroadcaster myInterceptorBroadcaster; @Autowired private FhirContext myFhirContext; + @Autowired + private EmpiResourceFilteringSvc myEmpiResourceFileringSvc; @Override public void handleMessage(Message theMessage) throws MessagingException { @@ -62,13 +65,14 @@ public class EmpiMessageHandler implements MessageHandler { ResourceModifiedMessage msg = ((ResourceModifiedJsonMessage) theMessage).getPayload(); try { - matchEmpiAndUpdateLinks(msg); + if (myEmpiResourceFileringSvc.shouldBeProcessed(getResourceFromPayload(msg))) { + matchEmpiAndUpdateLinks(msg); + } } catch (Exception e) { ourLog.error("Failed to handle EMPI Matching Resource:", e); throw e; } } - public void matchEmpiAndUpdateLinks(ResourceModifiedMessage theMsg) { String resourceType = theMsg.getId(myFhirContext).getResourceType(); validateResourceType(resourceType); diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java index 31da550d51a..7c64c923d59 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java @@ -47,6 +47,7 @@ import ca.uhn.fhir.jpa.empi.svc.EmpiLinkSvcImpl; import ca.uhn.fhir.jpa.empi.svc.EmpiLinkUpdaterSvcImpl; import ca.uhn.fhir.jpa.empi.svc.EmpiMatchFinderSvcImpl; import ca.uhn.fhir.jpa.empi.svc.EmpiMatchLinkSvc; +import ca.uhn.fhir.jpa.empi.svc.EmpiResourceFilteringSvc; import ca.uhn.fhir.jpa.empi.svc.EmpiPersonDeletingSvc; import ca.uhn.fhir.jpa.empi.svc.EmpiPersonMergerSvcImpl; import ca.uhn.fhir.jpa.empi.svc.EmpiResetSvcImpl; @@ -211,4 +212,9 @@ public class EmpiConsumerConfig { EmpiLinkDeleteSvc empiLinkDeleteSvc() { return new EmpiLinkDeleteSvc(); } + + @Bean + EmpiResourceFilteringSvc empiResourceFilteringSvc() { + return new EmpiResourceFilteringSvc(); + } } diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiResourceFilteringSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiResourceFilteringSvc.java new file mode 100644 index 00000000000..f1b425ebc03 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiResourceFilteringSvc.java @@ -0,0 +1,50 @@ +package ca.uhn.fhir.jpa.empi.svc; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class EmpiResourceFilteringSvc { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + private IEmpiSettings empiSettings; + @Autowired + EmpiSearchParamSvc myEmpiSearchParamSvc; + @Autowired + FhirContext myFhirContext; + + /** + * Given a resource from the EMPI Channel, determine whether or not EMPI processing should occur on it. + * + * EMPI processing should occur if for any {@link EmpiResourceSearchParamJson) Search Param, the resource contains a value. + * + * If the resource has no attributes that appear in the candidate search params, processing should be skipped, as there is not + * sufficient information to perform meaningful EMPI processing. (For example, how can EMPI processing occur on a patient that has _no_ attributes?) + * + * @param theResource the resource that you wish to check against EMPI rules. + * + * @return whether or not EMPI processing should proceed + */ + public boolean shouldBeProcessed(IAnyResource theResource) { + String resourceType = myFhirContext.getResourceType(theResource); + List candidateSearchParams = empiSettings.getEmpiRules().getCandidateSearchParams(); + + boolean containsValueForSomeSearchParam = candidateSearchParams.stream() + .filter(csp -> myEmpiSearchParamSvc.searchParamTypeIsValidForResourceType(csp.getResourceType(), resourceType)) + .flatMap(csp -> csp.getSearchParams().stream()) + .map(searchParam -> myEmpiSearchParamSvc.getValueFromResourceForSearchParam(theResource, searchParam)) + .anyMatch(valueList -> !valueList.isEmpty()); + + ourLog.debug("Is {} suitable for EMPI processing? : {}", theResource.getId(), containsValueForSomeSearchParam); + return containsValueForSomeSearchParam; + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiSearchParamSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiSearchParamSvc.java index b9fd9747aa3..9c033f4df6b 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiSearchParamSvc.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiSearchParamSvc.java @@ -87,10 +87,20 @@ public class EmpiSearchParamSvc implements ISearchParamRetriever { spMap = mapFromCriteria(theTargetType, theCriteria); } return spMap; - } + } - public ISearchBuilder generateSearchBuilderForType(String theTargetType) { + public ISearchBuilder generateSearchBuilderForType(String theTargetType) { IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theTargetType); return mySearchBuilderFactory.newSearchBuilder(resourceDao, theTargetType, resourceDao.getResourceType()); } + + /** + * Will return true if the types match, or the search param type is '*', otherwise false. + * @param theSearchParamType + * @param theResourceType + * @return + */ + public boolean searchParamTypeIsValidForResourceType(String theSearchParamType, String theResourceType) { + return theSearchParamType.equalsIgnoreCase(theResourceType) || theSearchParamType.equalsIgnoreCase("*"); + } } diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptorIT.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptorIT.java index 56b0bebbc76..e3575c743a7 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptorIT.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptorIT.java @@ -19,7 +19,6 @@ import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Person; -import org.hl7.fhir.r4.model.Practitioner; import org.hl7.fhir.r4.model.SearchParameter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -62,27 +61,21 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test { super.loadEmpiSearchParameters(); } - @Test - public void testCreatePatient() throws InterruptedException { - myEmpiHelper.createWithLatch(new Patient()); - assertLinkCount(1); - } - @Test public void testCreatePractitioner() throws InterruptedException { - myEmpiHelper.createWithLatch(new Practitioner()); + myEmpiHelper.createWithLatch(buildPractitionerWithNameAndId("somename", "some_id")); assertLinkCount(1); } @Test - public void testCreatePerson() throws InterruptedException { + public void testCreatePerson() { myPersonDao.create(new Person()); assertLinkCount(0); } @Test public void testDeletePersonDeletesLinks() throws InterruptedException { - myEmpiHelper.createWithLatch(new Patient()); + myEmpiHelper.createWithLatch(buildPaulPatient()); assertLinkCount(1); Person person = getOnlyActivePerson(); myPersonDao.delete(person.getIdElement()); @@ -102,6 +95,18 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test { } } + @Test + public void testCreatingPersonWithInsufficentEMPIAttributesIsNotEMPIProcessed() throws InterruptedException { + myEmpiHelper.doCreateResource(new Patient(), true); + assertLinkCount(0); + } + + @Test + public void testCreatingPatientWithOneOrMoreMatchingAttributesIsEMPIProcessed() throws InterruptedException { + myEmpiHelper.createWithLatch(buildPaulPatient()); + assertLinkCount(1); + } + @Test public void testCreateOrganizationWithEmpiTagForbidden() throws InterruptedException { //Creating a organization with the EMPI-MANAGED tag should fail @@ -163,7 +168,7 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test { public void testEmpiManagedPersonCannotBeModifiedByPersonUpdateRequest() throws InterruptedException { // When EMPI is enabled, only the EMPI system is allowed to modify Person links of Persons with the EMPI-MANAGED tag. Patient patient = new Patient(); - IIdType patientId = myEmpiHelper.createWithLatch(new Patient()).getDaoMethodOutcome().getId().toUnqualifiedVersionless(); + IIdType patientId = myEmpiHelper.createWithLatch(buildPaulPatient()).getDaoMethodOutcome().getId().toUnqualifiedVersionless(); patient.setId(patientId); @@ -263,7 +268,7 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test { @Test public void testPatientsWithNoEIDCanBeUpdated() throws InterruptedException { setPreventEidUpdates(true); - Patient p = new Patient(); + Patient p = buildPaulPatient(); EmpiHelperR4.OutcomeAndLogMessageWrapper wrapper = myEmpiHelper.createWithLatch(p); p.setId(wrapper.getDaoMethodOutcome().getId()); @@ -275,7 +280,7 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test { @Test public void testPatientsCanHaveEIDAddedInStrictMode() throws InterruptedException { setPreventEidUpdates(true); - Patient p = new Patient(); + Patient p = buildPaulPatient(); EmpiHelperR4.OutcomeAndLogMessageWrapper messageWrapper = myEmpiHelper.createWithLatch(p); p.setId(messageWrapper.getDaoMethodOutcome().getId()); addExternalEID(p, "external eid"); diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseLinkR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseLinkR4Test.java index 8e79c6092b3..83abb8c6f8e 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseLinkR4Test.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseLinkR4Test.java @@ -31,7 +31,7 @@ public abstract class BaseLinkR4Test extends BaseProviderR4Test { public void before() { super.before(); - myPatient = createPatientAndUpdateLinks(new Patient()); + myPatient = createPatientAndUpdateLinks(buildPaulPatient()); myPatientId = new StringType(myPatient.getIdElement().getValue()); myPerson = getPersonFromTarget(myPatient); diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderBatchR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderBatchR4Test.java index cb486ef00fb..d6244ca1dcd 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderBatchR4Test.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderBatchR4Test.java @@ -36,7 +36,7 @@ public class EmpiProviderBatchR4Test extends BaseLinkR4Test { @BeforeEach public void before() { super.before(); - myPractitioner = createPractitionerAndUpdateLinks(new Practitioner()); + myPractitioner = createPractitionerAndUpdateLinks(buildPractitionerWithNameAndId("some_pract", "some_pract_id")); myPractitionerId = new StringType(myPractitioner.getIdElement().getValue()); myPractitionerPerson = getPersonFromTarget(myPractitioner); myPractitionerPersonId = new StringType(myPractitionerPerson.getIdElement().getValue()); diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiBatchSvcImplTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiBatchSvcImplTest.java index 5a1c36a3bc4..5eaac317049 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiBatchSvcImplTest.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiBatchSvcImplTest.java @@ -17,7 +17,7 @@ import java.util.Date; class EmpiBatchSvcImplTest extends BaseEmpiR4Test { @Autowired - IEmpiBatchSvc myEmpiBatchSvc; + IEmpiBatchSvc myEmpiBatchSvc; @Autowired IInterceptorService myInterceptorService; diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiResourceFilteringSvcTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiResourceFilteringSvcTest.java new file mode 100644 index 00000000000..f0fbf66dca9 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiResourceFilteringSvcTest.java @@ -0,0 +1,39 @@ +package ca.uhn.fhir.jpa.empi.svc; + +import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.Patient; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +class EmpiResourceFilteringSvcTest extends BaseEmpiR4Test { + + @Autowired + private EmpiResourceFilteringSvc myEmpiResourceFilteringSvc; + + @Test + public void testFilterResourcesWhichHaveNoRelevantAttributes() { + Patient patient = new Patient(); + patient.setDeceased(new BooleanType(true)); //EMPI rules defined do not care about the deceased attribute. + + //SUT + boolean shouldBeProcessed = myEmpiResourceFilteringSvc.shouldBeProcessed(patient); + + assertThat(shouldBeProcessed, is(equalTo(false))); + } + + @Test + public void testDoNotFilterResourcesWithEMPIAttributes() { + Patient patient = new Patient(); + patient.addIdentifier().setValue("Hey I'm an ID! rules defined in empi-rules.json care about me!"); + + //SUT + boolean shouldBeProcessed = myEmpiResourceFilteringSvc.shouldBeProcessed(patient); + + assertThat(shouldBeProcessed, is(equalTo(true))); + } +} diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java index 48d2f546737..371d8edb3f6 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java @@ -399,6 +399,7 @@ public class SearchParamExtractorService { myInterceptorBroadcaster = theJpaInterceptorBroadcaster; } + @Nonnull public List extractParamValuesAsStrings(RuntimeSearchParam theActiveSearchParam, IBaseResource theResource) { return mySearchParamExtractor.extractParamValuesAsStrings(theActiveSearchParam, theResource); } diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiResourceSearchParamJson.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiResourceSearchParamJson.java index 6837ae4e56a..52c4c915310 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiResourceSearchParamJson.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiResourceSearchParamJson.java @@ -54,7 +54,7 @@ public class EmpiResourceSearchParamJson implements IModelJson, Iterable return this; } - private List getSearchParams() { + public List getSearchParams() { if (mySearchParams == null) { mySearchParams = new ArrayList<>(); }