This shockingly works

This commit is contained in:
Tadgh 2021-03-30 12:39:37 -04:00
parent a15ded7140
commit 419a5829ea
3 changed files with 377 additions and 31 deletions

View File

@ -58,7 +58,7 @@ public interface IMdmLinkDao extends JpaRepository<MdmLink, Long> {
Long getSourcePid();
}
@Query("SELECT ml.myGoldenResourcePid, ml.mySourcePid " +
@Query("SELECT ml.myGoldenResourcePid as goldenPid, ml.mySourcePid as sourcePid " +
"FROM MdmLink ml " +
"INNER JOIN MdmLink ml2 " +
"on ml.myGoldenResourcePid=ml2.myGoldenResourcePid " +

View File

@ -20,35 +20,22 @@ package ca.uhn.fhir.jpa.interceptor;
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.mdm.MdmLinkExpandSvc;
import ca.uhn.fhir.jpa.search.helper.SearchParamHelper;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.util.ClasspathUtil;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseConformance;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.slf4j.LoggerFactory.getLogger;
@ -62,24 +49,35 @@ public class MdmSearchExpandingInterceptorInterceptor {
@Autowired
private MdmLinkExpandSvc myMdmLinkExpandSvc;
@Autowired
private FhirContext myFhirContext;
@Autowired
private IdHelperService myIdHelperService;
@Hook(Pointcut.STORAGE_PRESEARCH_REGISTERED)
public void hook(RequestDetails theRequestDetails, SearchParameterMap theSearchParameterMap) {
System.out.println("zoop");
theSearchParameterMap.values().stream()
.flatMap(Collection::stream)
.flatMap(Collection::stream)
.filter(param -> param instanceof ReferenceParam)
.map(untypedParam -> (ReferenceParam)untypedParam)
.filter(ReferenceParam::isMdmExpand)
.forEach(mdmReferenceParam -> {
Set<String> strings = myMdmLinkExpandSvc.expandMdmBySourceResourceId(new IdDt(mdmReferenceParam.getValue()));
System.out.println(String.join(",", strings));
//TODO in AM, start here with a test that actually has an expansion to expand against.
});
public void hook(SearchParameterMap theSearchParameterMap) {
for (List<List<IQueryParameterType>> andList : theSearchParameterMap.values()) {
for (List<IQueryParameterType> orList : andList) {
expandAnyReferenceParameters(orList);
}
}
}
/**
* If a Parameter is a reference parameter, and it has been set to expand MDM, perform the expansion.
*/
private void expandAnyReferenceParameters(List<IQueryParameterType> orList) {
List<IQueryParameterType> toRemove = new ArrayList<>();
List<IQueryParameterType> toAdd = new ArrayList<>();
for (IQueryParameterType iQueryParameterType : orList) {
if (iQueryParameterType instanceof ReferenceParam) {
ReferenceParam refParam = (ReferenceParam) iQueryParameterType;
if (refParam.isMdmExpand()) {
Set<String> strings = myMdmLinkExpandSvc.expandMdmBySourceResourceId(new IdDt(refParam.getValue()));
if (!strings.isEmpty()) {
toRemove.add(refParam);
strings.stream().map(resourceId -> new ReferenceParam(refParam.getResourceType() + "/" + resourceId)).forEach(toAdd::add);
}
}
}
}
orList.removeAll(toRemove);
orList.addAll(toAdd);
}
}

View File

@ -0,0 +1,348 @@
package ca.uhn.fhir.jpa.mdm.interceptor;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.jpa.mdm.helper.MdmHelperConfig;
import ca.uhn.fhir.jpa.mdm.helper.MdmHelperR4;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.mdm.model.CanonicalEID;
import ca.uhn.fhir.mdm.rules.config.MdmSettings;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.TransactionLogMessages;
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Medication;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import java.util.Date;
import java.util.List;
import static ca.uhn.fhir.mdm.api.MdmConstants.CODE_GOLDEN_RECORD;
import static ca.uhn.fhir.mdm.api.MdmConstants.CODE_GOLDEN_RECORD_REDIRECTED;
import static ca.uhn.fhir.mdm.api.MdmConstants.CODE_HAPI_MDM_MANAGED;
import static ca.uhn.fhir.mdm.api.MdmConstants.SYSTEM_GOLDEN_RECORD_STATUS;
import static ca.uhn.fhir.mdm.api.MdmConstants.SYSTEM_MDM_MANAGED;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import static org.slf4j.LoggerFactory.getLogger;
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
@ContextConfiguration(classes = {MdmHelperConfig.class})
public class MdmSearchExpandingInterceptorIT extends BaseMdmR4Test {
private static final Logger ourLog = getLogger(MdmSearchExpandingInterceptorIT.class);
@RegisterExtension
@Autowired
public MdmHelperR4 myMdmHelper;
@Autowired
private IdHelperService myIdHelperService;
@Test
public void testCreatePractitioner() throws InterruptedException {
MdmHelperR4.OutcomeAndLogMessageWrapper withLatch = myMdmHelper.createWithLatch(addExternalEID(buildJanePatient(), "123"));
IIdType id = withLatch.getDaoMethodOutcome().getId();
myMdmHelper.createWithLatch(addExternalEID(buildJanePatient(), "123"));
myMdmHelper.createWithLatch(addExternalEID(buildJanePatient(), "123"));
myMdmHelper.createWithLatch(addExternalEID(buildJanePatient(), "123"));
assertLinkCount(4);
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap.setLoadSynchronous(true);
ReferenceOrListParam referenceOrListParam = new ReferenceOrListParam();
referenceOrListParam.addOr(new ReferenceParam(id.toVersionless()).setMdmExpand(true));
referenceOrListParam.addOr(new ReferenceParam(id.toVersionless()));
referenceOrListParam.addOr(new ReferenceParam(id.toVersionless()));
searchParameterMap.add(Observation.SP_SUBJECT, referenceOrListParam);
searchParameterMap.add(Observation.SP_CATEGORY, new TokenParam("test-1", "test-2"));
searchParameterMap.add(Observation.SP_ENCOUNTER, new ReferenceParam("Encounter/abc"));
myObservationDao.search(searchParameterMap);
}
@Test
public void testSearchExpandingInterceptorWorks() {
SearchParameterMap subject = new SearchParameterMap("subject", new ReferenceParam("Patient/123").setMdmExpand(true)).setLoadSynchronous(true);
myObservationDao.search(subject);
}
@Test
public void testDeleteGoldenResourceDeletesLinks() throws InterruptedException {
myMdmHelper.createWithLatch(buildPaulPatient());
assertLinkCount(1);
Patient sourcePatient = getOnlyGoldenPatient();
myPatientDao.delete(sourcePatient.getIdElement());
assertLinkCount(0);
}
@Test
public void testCreatePatientWithMdmTagForbidden() throws InterruptedException {
//Creating a golden resource with the MDM-MANAGED tag should fail
Patient patient = new Patient();
patient.getMeta().addTag(SYSTEM_MDM_MANAGED, CODE_HAPI_MDM_MANAGED, "User is managed by MDM");
try {
myMdmHelper.doCreateResource(patient, true);
fail();
} catch (ForbiddenOperationException e) {
assertThat(e.getMessage(), startsWith("Cannot create or modify Resources that are managed by MDM."));
}
}
@Test
public void testCreatePatientWithGoldenRecordTagForbidden() throws InterruptedException {
Patient patient = new Patient();
patient.getMeta().addTag(SYSTEM_GOLDEN_RECORD_STATUS, CODE_GOLDEN_RECORD, "Golden Record");
try {
myMdmHelper.doCreateResource(patient, true);
fail();
} catch (ForbiddenOperationException e) {
assertThat(e.getMessage(), startsWith("Cannot create or modify Resources that are managed by MDM."));
}
}
@Test
public void testCreateMedicationWithGoldenRecordRedirectTagForbidden() throws InterruptedException {
Medication medication = new Medication();
medication.getMeta().addTag(SYSTEM_GOLDEN_RECORD_STATUS, CODE_GOLDEN_RECORD_REDIRECTED, "Golden Record");
try {
myMdmHelper.doCreateResource(medication, true);
fail();
} catch (ForbiddenOperationException e) {
assertThat(e.getMessage(), startsWith("Cannot create or modify Resources that are managed by MDM."));
}
}
@Test
public void testCreatingGoldenResourceWithInsufficentMDMAttributesIsNotMDMProcessed() throws InterruptedException {
myMdmHelper.doCreateResource(new Patient(), true);
assertLinkCount(0);
}
@Test
public void testCreatingPatientWithOneOrMoreMatchingAttributesIsMDMProcessed() throws InterruptedException {
myMdmHelper.createWithLatch(buildPaulPatient());
assertLinkCount(1);
}
@Test
public void testCreateOrganizationWithMdmTagForbidden() throws InterruptedException {
//Creating a organization with the MDM-MANAGED tag should fail
Organization organization = new Organization();
organization.getMeta().addTag(SYSTEM_MDM_MANAGED, CODE_HAPI_MDM_MANAGED, "User is managed by MDM");
try {
myMdmHelper.doCreateResource(organization, true);
fail();
} catch (ForbiddenOperationException e) {
assertThat(e.getMessage(), startsWith("Cannot create or modify Resources that are managed by MDM."));
}
}
@Test
public void testUpdateOrganizationWithMdmTagForbidden() throws InterruptedException {
//Creating a organization with the MDM-MANAGED tag should fail
Organization organization = new Organization();
myMdmHelper.doCreateResource(organization, true);
organization.getMeta().addTag(SYSTEM_MDM_MANAGED, CODE_HAPI_MDM_MANAGED, "User is managed by MDM");
try {
myMdmHelper.doUpdateResource(organization, true);
fail();
} catch (ForbiddenOperationException e) {
assertEquals("The HAPI-MDM tag on a resource may not be changed once created.", e.getMessage());
}
}
@Test
public void testGoldenResourceRecordsManagedByMdmAllShareSameTag() throws InterruptedException {
myMdmHelper.createWithLatch(buildJanePatient());
myMdmHelper.createWithLatch(buildPaulPatient());
//TODO GGG MDM: this test is out of date, since we now are using golden record Patients
IBundleProvider search = myPatientDao.search(buildGoldenResourceSearchParameterMap());
List<IBaseResource> resources = search.getResources(0, search.size());
for (IBaseResource r : resources) {
assertThat(r.getMeta().getTag(SYSTEM_MDM_MANAGED, CODE_HAPI_MDM_MANAGED), is(notNullValue()));
}
}
@Test
public void testNonMdmManagedGoldenResourceCannotHaveMdmManagedTagAddedToThem() {
// GoldenResource created manually.
Patient patient = new Patient();
DaoMethodOutcome daoMethodOutcome = myMdmHelper.doCreateResource(patient, true);
assertNotNull(daoMethodOutcome.getId());
//Updating that patient to set them as MDM managed is not allowed.
patient.getMeta().addTag(SYSTEM_MDM_MANAGED, CODE_HAPI_MDM_MANAGED, "User is managed by MDM");
try {
myMdmHelper.doUpdateResource(patient, true);
fail();
} catch (ForbiddenOperationException e) {
assertEquals("The HAPI-MDM tag on a resource may not be changed once created.", e.getMessage());
}
}
@Test
public void testMdmManagedGoldenResourceCannotBeModifiedByGoldenResourceUpdateRequest() throws InterruptedException {
// When MDM is enabled, only the MDM system is allowed to modify GoldenResource links of GoldenResources with the MDM-MANAGED tag.
Patient patient = new Patient();
IIdType patientId = myMdmHelper.createWithLatch(buildPaulPatient()).getDaoMethodOutcome().getId().toUnqualifiedVersionless();
patient.setId(patientId);
// Updating a Golden Resource Patient who was created via MDM should fail.
MdmLink mdmLink = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(myIdHelperService.getPidOrNull(patient)).get();
Long sourcePatientPid = mdmLink.getGoldenResourcePid();
Patient goldenResourcePatient = (Patient) myPatientDao.readByPid(new ResourcePersistentId(sourcePatientPid));
goldenResourcePatient.setGender(Enumerations.AdministrativeGender.MALE);
try {
myMdmHelper.doUpdateResource(goldenResourcePatient, true);
fail();
} catch (ForbiddenOperationException e) {
assertThat(e.getMessage(), startsWith("Cannot create or modify Resources that are managed by MDM."));
}
}
@Test
public void testMdmPointcutReceivesTransactionLogMessages() throws InterruptedException {
MdmHelperR4.OutcomeAndLogMessageWrapper wrapper = myMdmHelper.createWithLatch(buildJanePatient());
TransactionLogMessages mdmTransactionLogMessages = wrapper.getLogMessages();
//There is no TransactionGuid here as there is no TransactionLog in this context.
assertThat(mdmTransactionLogMessages.getTransactionGuid(), is(nullValue()));
List<String> messages = mdmTransactionLogMessages.getValues();
assertThat(messages.isEmpty(), is(false));
}
@Test
public void testWhenASingularPatientUpdatesExternalEidThatGoldenResourceEidIsUpdated() throws InterruptedException {
Patient jane = addExternalEID(buildJanePatient(), "some_eid");
MdmHelperR4.OutcomeAndLogMessageWrapper latch = myMdmHelper.createWithLatch(jane);
jane.setId(latch.getDaoMethodOutcome().getId());
clearExternalEIDs(jane);
jane = addExternalEID(jane, "some_new_eid");
MdmHelperR4.OutcomeAndLogMessageWrapper outcomeWrapper = myMdmHelper.updateWithLatch(jane);
IAnyResource patient = getGoldenResourceFromTargetResource(jane);
List<CanonicalEID> externalEids = myEIDHelper.getExternalEid(patient);
assertThat(externalEids, hasSize(1));
assertThat("some_new_eid", is(equalTo(externalEids.get(0).getValue())));
}
@Test
public void testWhenEidUpdatesAreDisabledForbidsUpdatesToEidsOnTargets() throws InterruptedException {
setPreventEidUpdates(true);
Patient jane = addExternalEID(buildJanePatient(), "some_eid");
MdmHelperR4.OutcomeAndLogMessageWrapper latch = myMdmHelper.createWithLatch(jane);
jane.setId(latch.getDaoMethodOutcome().getId());
clearExternalEIDs(jane);
jane = addExternalEID(jane, "some_new_eid");
try {
myMdmHelper.doUpdateResource(jane, true);
fail();
} catch (ForbiddenOperationException e) {
assertThat(e.getMessage(), is(equalTo("While running with EID updates disabled, EIDs may not be updated on source resources")));
}
setPreventEidUpdates(false);
}
@Test
public void testWhenMultipleEidsAreDisabledThatTheInterceptorRejectsCreatesWithThem() {
setPreventMultipleEids(true);
Patient patient = buildJanePatient();
addExternalEID(patient, "123");
addExternalEID(patient, "456");
try {
myMdmHelper.doCreateResource(patient, true);
fail();
} catch (ForbiddenOperationException e) {
assertThat(e.getMessage(), is(equalTo("While running with multiple EIDs disabled, source resources may have at most one EID.")));
}
setPreventMultipleEids(false);
}
@Test
public void testInterceptorHandlesNonMdmResources() {
setPreventEidUpdates(true);
//Create some arbitrary resource.
SearchParameter fooSp = new SearchParameter();
fooSp.setCode("foo");
fooSp.addBase("Bundle");
fooSp.setType(Enumerations.SearchParamType.REFERENCE);
fooSp.setTitle("FOO SP");
fooSp.setExpression("Bundle.entry[0].resource.as(Composition).encounter");
fooSp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(Enumerations.PublicationStatus.ACTIVE);
myMdmHelper.doCreateResource(fooSp, true);
fooSp.setXpathUsage(SearchParameter.XPathUsageType.PHONETIC);
myMdmHelper.doUpdateResource(fooSp, true);
}
@Test
public void testPatientsWithNoEIDCanBeUpdated() throws InterruptedException {
setPreventEidUpdates(true);
Patient p = buildPaulPatient();
MdmHelperR4.OutcomeAndLogMessageWrapper wrapper = myMdmHelper.createWithLatch(p);
p.setId(wrapper.getDaoMethodOutcome().getId());
p.setBirthDate(new Date());
myMdmHelper.updateWithLatch(p);
setPreventEidUpdates(false);
}
@Test
public void testPatientsCanHaveEIDAddedInStrictMode() throws InterruptedException {
setPreventEidUpdates(true);
Patient p = buildPaulPatient();
MdmHelperR4.OutcomeAndLogMessageWrapper messageWrapper = myMdmHelper.createWithLatch(p);
p.setId(messageWrapper.getDaoMethodOutcome().getId());
addExternalEID(p, "external eid");
myMdmHelper.updateWithLatch(p);
setPreventEidUpdates(false);
}
private void setPreventEidUpdates(boolean thePrevent) {
((MdmSettings) myMdmSettings).setPreventEidUpdates(thePrevent);
}
private void setPreventMultipleEids(boolean thePrevent) {
((MdmSettings) myMdmSettings).setPreventMultipleEids(thePrevent);
}
}