adding pre-member-match hook

This commit is contained in:
leif stawnyczy 2023-11-22 14:51:51 -05:00
parent 41d9abf6ac
commit d36be49ad4
6 changed files with 160 additions and 7 deletions

View File

@ -46,6 +46,7 @@ import javax.annotation.Nonnull;
* <li>SUBSCRIPTION_xxx: Hooks on the HAPI FHIR Subscription framework</li>
* <li>STORAGE_xxx: Hooks on the storage engine</li>
* <li>VALIDATION_xxx: Hooks on the HAPI FHIR Validation framework</li>
* <li>MDM_XXX: Hooks on Master Data Management framework</li>
* <li>JPA_PERFTRACE_xxx: Performance tracing hooks on the JPA server</li>
* </ul>
* </p>
@ -2461,6 +2462,28 @@ public enum Pointcut implements IPointcut {
MDM_SUBMIT(
void.class, "ca.uhn.fhir.rest.api.server.RequestDetails", "ca.uhn.fhir.mdm.model.mdmevents.MdmSubmitEvent"),
/**
* <b>P2P MemberMatch Pre Hook:</b>
* This hook is invoked whenever a $member-match call is made.
* It is invoked after basic parameter validation, but before the
* database operations of MemberMatch are performed and results returned.
* </p>
* <p>
* This hook will take the following parameters:
* </p>
* <ul>
* <li>
* ca.uhn.fhir.rest.api.server.RequestDetails - An object containing details about the request that is about to be processed.
* </li>
* <li>
* ca.uhn.fhir.jpa.model.MemberMatchPreHookEvent - An event with the member-match
* parameters, including the Patient, CoverageToMatch, CoverateToLink, and Consent objects.
* </li>
* </ul>
*/
P2P_MEMBER_MATCH_PRE_HOOK(
void.class, "ca.uhn.fhir.rest.api.server.RequestDetails", "ca.uhn.fhir.jpa.model.MemberMatchPreHookEvent"),
/**
* <b>JPA Hook:</b>
* This hook is invoked when a cross-partition reference is about to be

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.config.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.api.IDaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
@ -118,7 +119,9 @@ public class JpaR4Config {
@Bean
public MemberMatchR4ResourceProvider memberMatchR4ResourceProvider(
FhirContext theFhirContext, MemberMatcherR4Helper theMemberMatchR4Helper) {
return new MemberMatchR4ResourceProvider(theFhirContext, theMemberMatchR4Helper);
FhirContext theFhirContext, IInterceptorBroadcaster theIInterceptorBroadcaster, MemberMatcherR4Helper theMemberMatchR4Helper) {
return new MemberMatchR4ResourceProvider(theFhirContext,
theMemberMatchR4Helper,
theIInterceptorBroadcaster);
}
}

View File

@ -0,0 +1,47 @@
package ca.uhn.fhir.jpa.model;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Coverage;
import org.hl7.fhir.r4.model.Patient;
public class MemberMatchPreHookEvent {
private Patient myPatient;
private Coverage myCoverageToMatch;
private Coverage myCoverageToLink;
private Consent myConsent;
public Patient getPatient() {
return myPatient;
}
public void setPatient(Patient thePatient) {
myPatient = thePatient;
}
public Coverage getCoverageToMatch() {
return myCoverageToMatch;
}
public void setCoverageToMatch(Coverage theCoverageToMatch) {
myCoverageToMatch = theCoverageToMatch;
}
public Coverage getCoverageToLink() {
return myCoverageToLink;
}
public void setCoverageToLink(Coverage theCoverageToLink) {
myCoverageToLink = theCoverageToLink;
}
public Consent getConsent() {
return myConsent;
}
public void setConsent(Consent theConsent) {
myConsent = theConsent;
}
}

View File

@ -21,6 +21,10 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
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.model.MemberMatchPreHookEvent;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
@ -28,22 +32,29 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Coverage;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import java.util.Optional;
import javax.annotation.Nullable;
import java.util.Optional;
public class MemberMatchR4ResourceProvider {
private final MemberMatcherR4Helper myMemberMatcherR4Helper;
private final FhirContext myFhirContext;
private final IInterceptorBroadcaster myInterceptorBroadcaster;
public MemberMatchR4ResourceProvider(FhirContext theFhirContext, MemberMatcherR4Helper theMemberMatcherR4Helper) {
public MemberMatchR4ResourceProvider(
FhirContext theFhirContext,
MemberMatcherR4Helper theMemberMatcherR4Helper,
IInterceptorBroadcaster theIInterceptorBroadcaster
) {
myFhirContext = theFhirContext;
myMemberMatcherR4Helper = theMemberMatcherR4Helper;
myInterceptorBroadcaster = theIInterceptorBroadcaster;
}
/**
@ -127,6 +138,24 @@ public class MemberMatchR4ResourceProvider {
throw new UnprocessableEntityException(Msg.code(2147) + i18nMessage);
}
if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.P2P_MEMBER_MATCH_PRE_HOOK, myInterceptorBroadcaster, theRequestDetails)) {
MemberMatchPreHookEvent preHookParams = new MemberMatchPreHookEvent();
preHookParams.setConsent(theConsent);
preHookParams.setPatient(theMemberPatient);
preHookParams.setCoverageToMatch(theCoverageToMatch);
preHookParams.setCoverageToLink(theCoverageToLink);
HookParams params = new HookParams();
params.add(MemberMatchPreHookEvent.class, preHookParams);
params.add(RequestDetails.class, theRequestDetails);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster,
theRequestDetails,
Pointcut.P2P_MEMBER_MATCH_PRE_HOOK,
params
);
}
myMemberMatcherR4Helper.addMemberIdentifierToMemberPatient(theMemberPatient, patient.getIdentifierFirstRep());
myMemberMatcherR4Helper.updateConsentForMemberMatch(theConsent, patient, theMemberPatient, theRequestDetails);
return myMemberMatcherR4Helper.buildSuccessReturnParameters(theMemberPatient, theCoverageToLink, theConsent);
@ -137,8 +166,8 @@ public class MemberMatchR4ResourceProvider {
validateParam(theMemberPatient, Constants.PARAM_MEMBER_PATIENT);
validateParam(theOldCoverage, Constants.PARAM_OLD_COVERAGE);
validateParam(theNewCoverage, Constants.PARAM_NEW_COVERAGE);
validateParam(theConsent, Constants.PARAM_CONSENT);
validateMemberPatientParam(theMemberPatient);
validateParam(theConsent, Constants.PARAM_CONSENT);
validateConsentParam(theConsent);
}

View File

@ -46,10 +46,10 @@ import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import static ca.uhn.fhir.rest.api.Constants.PARAM_CONSENT;
import static ca.uhn.fhir.rest.api.Constants.PARAM_MEMBER_IDENTIFIER;

View File

@ -1,10 +1,15 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.model.MemberMatchPreHookEvent;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.client.apache.ResourceEntity;
import ca.uhn.fhir.util.ParametersUtil;
import com.google.common.collect.Lists;
@ -32,6 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static ca.uhn.fhir.rest.api.Constants.PARAM_CONSENT;
import static ca.uhn.fhir.rest.api.Constants.PARAM_CONSENT_PATIENT_REFERENCE;
@ -46,6 +52,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SuppressWarnings("Duplicates")
public class PatientMemberMatchOperationR4Test extends BaseResourceProviderR4Test {
@ -72,6 +79,9 @@ public class PatientMemberMatchOperationR4Test extends BaseResourceProviderR4Tes
@Autowired
MemberMatchR4ResourceProvider theMemberMatchR4ResourceProvider;
@Autowired
IInterceptorService myInterceptorService;
@BeforeEach
public void beforeDisableResultReuse() {
myStorageSettings.setReuseCachedSearchResultsForMillis(null);
@ -150,6 +160,48 @@ public class PatientMemberMatchOperationR4Test extends BaseResourceProviderR4Tes
myClient.create().resource(ourExistingCoverage).execute().getId().toUnqualifiedVersionless().getValue();
}
@Test
public void testMemberMatch_invokesPreMemberMatchHook() throws Exception {
// setup
AtomicBoolean setter = new AtomicBoolean();
createCoverageWithBeneficiary(true, true);
Parameters inputParameters = buildInputParameters(myPatient, oldCoverage, newCoverage, myConsent);
Object interceptor = new Object() {
@Hook(Pointcut.P2P_MEMBER_MATCH_PRE_HOOK)
void validateConsent(RequestDetails theRequestDetails, MemberMatchPreHookEvent theEvent) {
assertNotNull(theRequestDetails);
assertNotNull(theEvent);
assertNotNull(theEvent.getConsent());
assertNotNull(theEvent.getCoverageToMatch());
assertNotNull(theEvent.getCoverageToLink());
assertNotNull(theEvent.getPatient());
assertEquals(myPatient.getId(), theEvent.getPatient().getId());
assertEquals(oldCoverage.getId(), theEvent.getCoverageToMatch().getIdElement().getIdPart());
assertEquals(newCoverage.getId(), theEvent.getCoverageToLink().getIdElement().getIdPart());
assertEquals(myConsent.getId(), theEvent.getConsent().getId());
setter.getAndSet(true);
}
};
try {
myInterceptorService.registerInterceptor(interceptor);
// test
Parameters parametersResponse = performOperation(myServerBase + ourQuery,
EncodingEnum.JSON, inputParameters);
validateMemberPatient(parametersResponse);
validateNewCoverage(parametersResponse, newCoverage);
} finally {
myInterceptorService.unregisterInterceptor(interceptor);
}
assertTrue(setter.get());
}
@Test
public void testMemberMatchByCoverageId() throws Exception {
createCoverageWithBeneficiary(true, true);
@ -162,7 +214,6 @@ public class PatientMemberMatchOperationR4Test extends BaseResourceProviderR4Tes
validateNewCoverage(parametersResponse, newCoverage);
}
@Test
public void testCoverageNoBeneficiaryReturns422() throws Exception {
createCoverageWithBeneficiary(false, false);