Correct bug with attempting to modify an EID

This commit is contained in:
Tadgh 2020-11-13 11:45:24 -05:00
parent f648d8ec2b
commit 68fad00a29
18 changed files with 183 additions and 141 deletions

View File

@ -65,7 +65,7 @@ public class EmpiSearchParameterLoader {
org.hl7.fhir.dstu3.model.SearchParameter retval = new org.hl7.fhir.dstu3.model.SearchParameter();
retval.setId(EMPI_PERSON_ASSURANCE_SEARCH_PARAMETER_ID);
retval.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_MDM_MANAGED).setCode(EmpiConstants.CODE_HAPI_MDM_MANAGED);
retval.setCode("assurance");
retval.addBase("Person");
retval.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
@ -78,7 +78,7 @@ public class EmpiSearchParameterLoader {
SearchParameter retval = new SearchParameter();
retval.setId(EMPI_PERSON_ASSURANCE_SEARCH_PARAMETER_ID);
retval.setStatus(Enumerations.PublicationStatus.ACTIVE);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_MDM_MANAGED).setCode(EmpiConstants.CODE_HAPI_MDM_MANAGED);
retval.setCode("assurance");
retval.addBase("Person");
retval.setType(Enumerations.SearchParamType.TOKEN);
@ -91,7 +91,7 @@ public class EmpiSearchParameterLoader {
org.hl7.fhir.dstu3.model.SearchParameter retval = new org.hl7.fhir.dstu3.model.SearchParameter();
retval.setId(EMPI_PERSON_ACTIVE_SEARCH_PARAMETER_ID);
retval.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_MDM_MANAGED).setCode(EmpiConstants.CODE_HAPI_MDM_MANAGED);
retval.setCode("active");
retval.addBase("Person");
retval.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
@ -104,7 +104,7 @@ public class EmpiSearchParameterLoader {
SearchParameter retval = new SearchParameter();
retval.setId(EMPI_PERSON_ACTIVE_SEARCH_PARAMETER_ID);
retval.setStatus(Enumerations.PublicationStatus.ACTIVE);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_MDM_MANAGED).setCode(EmpiConstants.CODE_HAPI_MDM_MANAGED);
retval.setCode("active");
retval.addBase("Person");
retval.setType(Enumerations.SearchParamType.TOKEN);

View File

@ -89,7 +89,7 @@ public class EmpiSubscriptionLoader {
retval.setReason("EMPI");
retval.setStatus(org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus.REQUESTED);
retval.setCriteria(theCriteria);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_MDM_MANAGED).setCode(EmpiConstants.CODE_HAPI_MDM_MANAGED);
org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelComponent channel = retval.getChannel();
channel.setType(org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType.MESSAGE);
channel.setEndpoint("channel:" + myChannelNamer.getChannelName(IEmpiSettings.EMPI_CHANNEL_NAME, new ChannelProducerSettings()));
@ -103,7 +103,7 @@ public class EmpiSubscriptionLoader {
retval.setReason("EMPI");
retval.setStatus(Subscription.SubscriptionStatus.REQUESTED);
retval.setCriteria(theCriteria);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_MDM_MANAGED).setCode(EmpiConstants.CODE_HAPI_MDM_MANAGED);
Subscription.SubscriptionChannelComponent channel = retval.getChannel();
channel.setType(Subscription.SubscriptionChannelType.MESSAGE);
channel.setEndpoint("channel:" + myChannelNamer.getChannelName(IEmpiSettings.EMPI_CHANNEL_NAME, new ChannelProducerSettings()));

View File

@ -161,7 +161,7 @@ public class EmpiStorageInterceptor implements IEmpiStorageInterceptor {
}
private void throwBlockEmpiManagedTagChange() {
throw new ForbiddenOperationException("The " + EmpiConstants.CODE_HAPI_EMPI_MANAGED + " tag on a resource may not be changed once created.");
throw new ForbiddenOperationException("The " + EmpiConstants.CODE_HAPI_MDM_MANAGED + " tag on a resource may not be changed once created.");
}
private void throwModificationBlockedByEmpi() {

View File

@ -111,7 +111,7 @@ public class EmpiEidUpdateService {
private void createNewPersonAndFlagAsDuplicate(IAnyResource theResource, MdmTransactionContext theMdmTransactionContext, IAnyResource theOldPerson) {
log(theMdmTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
IAnyResource newPerson = myPersonHelper.createSourceResourceFromEmpiTarget(theResource);
IAnyResource newPerson = myPersonHelper.createGoldenResourceFromMdmTarget(theResource);
myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.AUTO, theMdmTransactionContext);
myEmpiLinkSvc.updateLink(newPerson, theOldPerson, EmpiMatchOutcome.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theMdmTransactionContext);

View File

@ -53,7 +53,7 @@ public class EmpiMatchLinkSvc {
@Autowired
private EmpiSourceResourceFindingSvc myEmpiSourceResourceFindingSvc;
@Autowired
private PersonHelper myPersonHelper;
private PersonHelper myGoldenResourceHelper;
@Autowired
private EmpiEidUpdateService myEidUpdateService;
@ -122,7 +122,7 @@ public class EmpiMatchLinkSvc {
private void handleEmpiWithNoCandidates(IAnyResource theResource, MdmTransactionContext theMdmTransactionContext) {
log(theMdmTransactionContext, String.format("There were no matched candidates for EMPI, creating a new %s.", theResource.getIdElement().getResourceType()));
IAnyResource newPerson = myPersonHelper.createSourceResourceFromEmpiTarget(theResource);
IAnyResource newPerson = myGoldenResourceHelper.createGoldenResourceFromMdmTarget(theResource);
// TODO GGG :)
// 1. Get the right helper
// 2. Create source resoruce for the EMPI target
@ -135,14 +135,14 @@ public class EmpiMatchLinkSvc {
log(theMdmTransactionContext, "EMPI has narrowed down to one candidate for matching.");
IAnyResource sourceResource = myEmpiSourceResourceFindingSvc.getSourceResourceFromMatchedSourceResourceCandidate(thePersonCandidate, theMdmTransactionContext.getResourceType());
if (myPersonHelper.isPotentialDuplicate(sourceResource, theTargetResource)) {
if (myGoldenResourceHelper.isPotentialDuplicate(sourceResource, theTargetResource)) {
log(theMdmTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
IAnyResource newSourceResource = myPersonHelper.createSourceResourceFromEmpiTarget(theTargetResource);
IAnyResource newSourceResource = myGoldenResourceHelper.createGoldenResourceFromMdmTarget(theTargetResource);
myEmpiLinkSvc.updateLink(newSourceResource, theTargetResource, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.AUTO, theMdmTransactionContext);
myEmpiLinkSvc.updateLink(newSourceResource, sourceResource, EmpiMatchOutcome.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theMdmTransactionContext);
} else {
if (thePersonCandidate.isMatch()) {
myPersonHelper.handleExternalEidAddition(sourceResource, theTargetResource, theMdmTransactionContext);
myGoldenResourceHelper.handleExternalEidAddition(sourceResource, theTargetResource, theMdmTransactionContext);
// myPersonHelper.updatePersonFromNewlyCreatedEmpiTarget(person, theResource, theEmpiTransactionContext);
}
myEmpiLinkSvc.updateLink(sourceResource, theTargetResource, thePersonCandidate.getMatchResult(), EmpiLinkSourceEnum.AUTO, theMdmTransactionContext);

View File

@ -22,27 +22,23 @@ package ca.uhn.fhir.jpa.empi.svc;
import ca.uhn.fhir.empi.api.EmpiConstants;
import ca.uhn.fhir.empi.api.IEmpiSettings;
import ca.uhn.fhir.empi.util.EIDHelper;
import ca.uhn.fhir.empi.util.EmpiUtil;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
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.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class EmpiResourceDaoSvc {
@ -63,42 +59,52 @@ public class EmpiResourceDaoSvc {
}
}
/**
* Given a resource, remove its Golden Resource tag.
* @param theGoldenResource the {@link IAnyResource} to remove the tag from.
* @param theResourcetype the type of that resource
*/
public void removeGoldenResourceTag(IAnyResource theGoldenResource, String theResourcetype) {
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theResourcetype);
resourceDao.removeTag(theGoldenResource.getIdElement(), TagTypeEnum.TAG, EmpiConstants.SYSTEM_GOLDEN_RECORD_STATUS, EmpiConstants.CODE_GOLDEN_RECORD);
}
public IAnyResource readSourceResourceByPid(ResourcePersistentId theSourceResourcePid, String theResourceType) {
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theResourceType);
return (IAnyResource) resourceDao.readByPid(theSourceResourcePid);
}
//TODO GGG MDM address this
public Optional<IAnyResource> searchSourceResourceByEID(String theEid, String theResourceType) {
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add("identifier", new TokenParam(myEmpiConfig.getEmpiRules().getEnterpriseEIDSystem(), theEid));
// TODO NG - During person dedup do we set this to false? We might be setting a person to inactive...
map.add("active", new TokenParam("true"));
// map.add("_tag", new TokenParam(EmpiConstants.SYSTEM_EMPI_MANAGED, EmpiConstants.CODE_HAPI_EMPI_MANAGED));
SearchParameterMap map = buildEidSearchParameterMap(theEid);
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theResourceType);
IBundleProvider search = resourceDao.search(map);
List<IBaseResource> resources = search.getResources(0, MAX_MATCHING_PERSONS);
// Could add the meta tag to the query, but it's probably more efficient to filter on it afterwards since in practice
// it will always be present.
List<IBaseResource> list = search.getResources(0, MAX_MATCHING_PERSONS).stream()
.filter(EmpiUtil::isEmpiManaged)
.collect(Collectors.toList());
if (list.isEmpty()) {
if (resources.isEmpty()) {
return Optional.empty();
} else if (list.size() > 1) {
} else if (resources.size() > 1) {
throw new InternalErrorException("Found more than one active " +
EmpiConstants.CODE_HAPI_EMPI_MANAGED +
EmpiConstants.CODE_HAPI_MDM_MANAGED +
" Person with EID " +
theEid +
": " +
list.get(0).getIdElement().getValue() +
resources.get(0).getIdElement().getValue() +
", " +
list.get(1).getIdElement().getValue()
resources.get(1).getIdElement().getValue()
);
} else {
return Optional.of((IAnyResource) list.get(0));
return Optional.of((IAnyResource) resources.get(0));
}
}
@NotNull
private SearchParameterMap buildEidSearchParameterMap(String theTheEid) {
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add("identifier", new TokenParam(myEmpiConfig.getEmpiRules().getEnterpriseEIDSystem(), theTheEid));
map.add("_tag", new TokenParam(EmpiConstants.SYSTEM_GOLDEN_RECORD_STATUS, EmpiConstants.CODE_GOLDEN_RECORD));
return map;
}
}

View File

@ -37,6 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@ -58,25 +59,27 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
@Override
@Transactional
public IAnyResource mergeGoldenResources(IAnyResource theFromGoldenResource, IAnyResource theToGoldenResource, MdmTransactionContext theMdmTransactionContext) {
Long fromGoldenResourcePid = myIdHelperService.getPidOrThrowException(theFromGoldenResource);
Long toGoldenResourcePid = myIdHelperService.getPidOrThrowException(theToGoldenResource);
String resourceType = theMdmTransactionContext.getResourceType();
//Merge attributes, to be determined when survivorship is solved.
myPersonHelper.mergeFields(theFromGoldenResource, theToGoldenResource);
//Merge the links from the FROM to the TO resource. Clean up dangling links.
mergeGoldenResourceLinks(theFromGoldenResource, theToGoldenResource, toGoldenResourcePid, theMdmTransactionContext);
//TODO GGG MDM: Is this just cleanup? In theory, the merge step from before shoulda done this..
removeTargetLinks(theFromGoldenResource, theToGoldenResource, theMdmTransactionContext);
//Create the new REDIRECT link
addMergeLink(toGoldenResourcePid, fromGoldenResourcePid, resourceType);
//Strip the golden resource tag from the now-deprecated resource.
myEmpiResourceDaoSvc.removeGoldenResourceTag(theFromGoldenResource, resourceType);
myEmpiResourceDaoSvc.upsertSourceResource(theFromGoldenResource, theMdmTransactionContext.getResourceType());
//Add the REDIRECT tag to that same deprecated resource.
myPersonHelper.deactivateResource(theFromGoldenResource);
Long fromGoldenResourcePid = myIdHelperService.getPidOrThrowException(theFromGoldenResource);
addMergeLink(toGoldenResourcePid, fromGoldenResourcePid, theMdmTransactionContext.getResourceType());
//myPersonHelper.deactivateResource(theFromGoldenResource);
//Remove HAPI-EMPI Managed TAG, add a different Ttag? e.g. HAPI-EMPI-DEPRECATED or something? This would serve the purpose.
myEmpiResourceDaoSvc.upsertSourceResource(theFromGoldenResource, theMdmTransactionContext.getResourceType());
//Save the deprecated resource.
myEmpiResourceDaoSvc.upsertSourceResource(theFromGoldenResource, resourceType);
log(theMdmTransactionContext, "Merged " + theFromGoldenResource.getIdElement().toVersionless() + " into " + theToGoldenResource.getIdElement().toVersionless());
return theToGoldenResource;
@ -113,22 +116,36 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
}
/**
* Helper method which performs merger of links between resources, and cleans up dangling links afterwards.
*
* For each incomingLink, either ignore it, move it, or replace the original one
* 1. If the link already exists on the TO resource, and it is an automatic link, ignore the link, and subsequently delete it.
* 2. If the link does not exist on the TO resource, redirect the link from the FROM resource to the TO resource
* 3. If an incoming link is MANUAL, and theres a matching link on the FROM resource which is AUTOMATIC, the manual link supercedes the automatic one.
* 4. Manual link collisions cause invalid request exception.
*
* @param theFromResource
* @param theToResource
* @param theToResourcePid
* @param theMdmTransactionContext
*/
private void mergeGoldenResourceLinks(IAnyResource theFromResource, IAnyResource theToResource, Long theToResourcePid, MdmTransactionContext theMdmTransactionContext) {
List<EmpiLink> fromLinks = myEmpiLinkDaoSvc.findEmpiLinksByGoldenResource(theFromResource); // fromLinks - links going to theFromResource
List<EmpiLink> toLinks = myEmpiLinkDaoSvc.findEmpiLinksByGoldenResource(theToResource); // toLinks - links going to theToResource
List<EmpiLink> toDelete = new ArrayList<>();
// For each incomingLink, either ignore it, move it, or replace the original one
for (EmpiLink fromLink : fromLinks) {
Optional<EmpiLink> optionalToLink = findFirstLinkWithMatchingTarget(toLinks, fromLink);
if (optionalToLink.isPresent()) {
// toLinks.remove(optionalToLink);
// The original links already contain this target, so move it over to the toResource
EmpiLink toLink = optionalToLink.get();
if (fromLink.isManual()) {
switch (toLink.getLinkSource()) {
case AUTO:
ourLog.trace("MANUAL overrides AUT0. Deleting link {}", toLink);
//3
log(theMdmTransactionContext, String.format("MANUAL overrides AUT0. Deleting link %s", toLink.toString()));
myEmpiLinkDaoSvc.deleteLink(toLink);
break;
case MANUAL:
@ -137,15 +154,18 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
}
}
} else {
// Ignore the case where the incoming link is AUTO
//1
toDelete.add(fromLink);
continue;
}
}
// The original links didn't contain this target, so move it over to the toPerson
//2 The original TO links didn't contain this target, so move it over to the toGoldenResource
fromLink.setGoldenResourcePid(theToResourcePid);
ourLog.trace("Saving link {}", fromLink);
myEmpiLinkDaoSvc.save(fromLink);
}
//1 Delete dangling links
toDelete.forEach(link -> myEmpiLinkDaoSvc.deleteLink(link));
}
private Optional<EmpiLink> findFirstLinkWithMatchingTarget(List<EmpiLink> theEmpiLinks, EmpiLink theLinkWithTargetToMatch) {

View File

@ -8,6 +8,7 @@ import ca.uhn.fhir.empi.api.IEmpiSettings;
import ca.uhn.fhir.empi.model.MdmTransactionContext;
import ca.uhn.fhir.empi.rules.svc.EmpiResourceMatcherSvc;
import ca.uhn.fhir.empi.util.EIDHelper;
import ca.uhn.fhir.empi.util.EmpiUtil;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
@ -48,6 +49,7 @@ import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Practitioner;
import org.hl7.fhir.r4.model.Reference;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
@ -159,10 +161,8 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
@Nonnull
protected Patient createGoldenPatient(Patient thePatient, boolean theEmpiManaged) {
if (theEmpiManaged) {
thePatient.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED);
thePatient.setActive(true);
}
EmpiUtil.setEmpiManaged(thePatient);
EmpiUtil.setGoldenResource(thePatient);
DaoMethodOutcome outcome = myPatientDao.create(thePatient);
Patient patient = (Patient) outcome.getResource();
patient.setId(outcome.getId());
@ -392,35 +392,34 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
return IsMatchedToAPerson.matchedToAPerson(myIdHelperService, myEmpiLinkDaoSvc);
}
protected Patient getOnlyActiveSourcePatient() {
List<IBaseResource> resources = getAllActiveGoldenPatients();
protected Patient getOnlyGoldenPatient() {
List<IBaseResource> resources = getAllGoldenPatients();
assertEquals(1, resources.size());
return (Patient) resources.get(0);
}
@Nonnull
protected List<IBaseResource> getAllActiveGoldenPatients() {
return getAllGoldenPatients(true);
}
@Nonnull
protected List<IBaseResource> getAllGoldenPatients() {
return getAllGoldenPatients(false);
return getPatientsByTag(EmpiConstants.CODE_GOLDEN_RECORD);
}
@Nonnull
private List<IBaseResource> getAllGoldenPatients(boolean theOnlyActive) {
protected List<IBaseResource> getAllRedirectedGoldenPatients() {
return getPatientsByTag(EmpiConstants.CODE_GOLDEN_RECORD_REDIRECTED);
}
@NotNull
private List<IBaseResource> getPatientsByTag(String theCode) {
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
//TODO GGG ensure that this tag search works effectively.
map.add("_tag", new TokenParam(EmpiConstants.SYSTEM_EMPI_MANAGED, EmpiConstants.CODE_HAPI_EMPI_MANAGED));
if (theOnlyActive) {
map.add("active", new TokenParam().setValue("true"));
}
map.add("_tag", new TokenParam(EmpiConstants.SYSTEM_GOLDEN_RECORD_STATUS, theCode));
IBundleProvider bundle = myPatientDao.search(map);
return bundle.getResources(0, 999);
}
@Nonnull
protected EmpiLink createResourcesAndBuildTestEmpiLink() {
Patient sourcePatient = createGoldenPatient();

View File

@ -32,8 +32,8 @@ import org.springframework.test.context.ContextConfiguration;
import java.util.Date;
import java.util.List;
import static ca.uhn.fhir.empi.api.EmpiConstants.CODE_HAPI_EMPI_MANAGED;
import static ca.uhn.fhir.empi.api.EmpiConstants.SYSTEM_EMPI_MANAGED;
import static ca.uhn.fhir.empi.api.EmpiConstants.CODE_HAPI_MDM_MANAGED;
import static ca.uhn.fhir.empi.api.EmpiConstants.SYSTEM_MDM_MANAGED;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
@ -72,7 +72,7 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test {
public void testDeletePersonDeletesLinks() throws InterruptedException {
myEmpiHelper.createWithLatch(buildPaulPatient());
assertLinkCount(1);
Patient sourcePatient = getOnlyActiveSourcePatient();
Patient sourcePatient = getOnlyGoldenPatient();
myPatientDao.delete(sourcePatient.getIdElement());
assertLinkCount(0);
}
@ -81,7 +81,7 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test {
public void testCreatePersonWithEmpiTagForbidden() throws InterruptedException {
//Creating a person with the EMPI-MANAGED tag should fail
Person person = new Person();
person.getMeta().addTag(SYSTEM_EMPI_MANAGED, CODE_HAPI_EMPI_MANAGED, "User is managed by EMPI");
person.getMeta().addTag(SYSTEM_MDM_MANAGED, CODE_HAPI_MDM_MANAGED, "User is managed by EMPI");
try {
myEmpiHelper.doCreateResource(person, true);
fail();
@ -106,7 +106,7 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test {
public void testCreateOrganizationWithEmpiTagForbidden() throws InterruptedException {
//Creating a organization with the EMPI-MANAGED tag should fail
Organization organization = new Organization();
organization.getMeta().addTag(SYSTEM_EMPI_MANAGED, CODE_HAPI_EMPI_MANAGED, "User is managed by EMPI");
organization.getMeta().addTag(SYSTEM_MDM_MANAGED, CODE_HAPI_MDM_MANAGED, "User is managed by EMPI");
try {
myEmpiHelper.doCreateResource(organization, true);
fail();
@ -120,7 +120,7 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test {
//Creating a organization with the EMPI-MANAGED tag should fail
Organization organization = new Organization();
myEmpiHelper.doCreateResource(organization, true);
organization.getMeta().addTag(SYSTEM_EMPI_MANAGED, CODE_HAPI_EMPI_MANAGED, "User is managed by EMPI");
organization.getMeta().addTag(SYSTEM_MDM_MANAGED, CODE_HAPI_MDM_MANAGED, "User is managed by EMPI");
try {
myEmpiHelper.doUpdateResource(organization, true);
fail();
@ -139,7 +139,7 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test {
List<IBaseResource> resources = search.getResources(0, search.size());
for (IBaseResource person : resources) {
assertThat(person.getMeta().getTag(SYSTEM_EMPI_MANAGED, CODE_HAPI_EMPI_MANAGED), is(notNullValue()));
assertThat(person.getMeta().getTag(SYSTEM_MDM_MANAGED, CODE_HAPI_MDM_MANAGED), is(notNullValue()));
}
}
@ -151,7 +151,7 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test {
assertNotNull(daoMethodOutcome.getId());
//Updating that person to set them as EMPI managed is not allowed.
person.getMeta().addTag(SYSTEM_EMPI_MANAGED, CODE_HAPI_EMPI_MANAGED, "User is managed by EMPI");
person.getMeta().addTag(SYSTEM_MDM_MANAGED, CODE_HAPI_MDM_MANAGED, "User is managed by EMPI");
try {
myEmpiHelper.doUpdateResource(person, true);
fail();

View File

@ -113,7 +113,7 @@ public class EmpiProviderClearLinkR4Test extends BaseLinkR4Test {
* @return
*/
private SearchParameterMap buildSourceResourceParameterMap() {
return new SearchParameterMap().setLoadSynchronous(true).add("_tag", new TokenParam(EmpiConstants.SYSTEM_EMPI_MANAGED, EmpiConstants.CODE_HAPI_EMPI_MANAGED));
return new SearchParameterMap().setLoadSynchronous(true).add("_tag", new TokenParam(EmpiConstants.SYSTEM_MDM_MANAGED, EmpiConstants.CODE_HAPI_MDM_MANAGED));
}
@Test

View File

@ -39,13 +39,11 @@ public class EmpiProviderMergePersonsR4Test extends BaseProviderR4Test {
@Test
public void testMerge() {
//TODO GGG RP fix
IBaseResource mergedSourcePatient = myEmpiProviderR4.mergeGoldenResources(myFromSourcePatientId, myToSourcePatientId, myRequestDetails);
assertEquals(myToSourcePatient.getIdElement(), mergedSourcePatient.getIdElement());
// TODO GGG RP FIX
//assertThat(mergedSourcePatient, is(sameSourceResourceAs(myToSourcePatient)));
assertEquals(2, getAllGoldenPatients().size());
assertEquals(1, getAllActiveGoldenPatients().size());
assertEquals(1, getAllRedirectedGoldenPatients().size());
assertEquals(1, getAllGoldenPatients().size());
Patient fromSourcePatient = myPatientDao.read(myFromSourcePatient.getIdElement().toUnqualifiedVersionless());
assertThat(fromSourcePatient.getActive(), is(false));

View File

@ -130,7 +130,7 @@ public class EmpiProviderUpdateLinkR4Test extends BaseLinkR4Test {
@Test
public void testExcludedPerson() {
Patient patient = new Patient();
patient.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_NO_EMPI_MANAGED);
patient.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_MDM_MANAGED).setCode(EmpiConstants.CODE_NO_EMPI_MANAGED);
createPatient(patient);
try {
myEmpiProviderR4.updateLink(mySourcePatientId, new StringType(patient.getIdElement().getValue()), NO_MATCH_RESULT, myRequestDetails);

View File

@ -303,7 +303,7 @@ public class EmpiMatchLinkSvcTest extends BaseEmpiR4Test {
public void testPatientWithNoEmpiTagIsNotMatched() {
// Patient with "no-empi" tag is not matched
Patient janePatient = buildJanePatient();
janePatient.getMeta().addTag(EmpiConstants.SYSTEM_EMPI_MANAGED, EmpiConstants.CODE_NO_EMPI_MANAGED, "Don't EMPI on me!");
janePatient.getMeta().addTag(EmpiConstants.SYSTEM_MDM_MANAGED, EmpiConstants.CODE_NO_EMPI_MANAGED, "Don't EMPI on me!");
createPatientAndUpdateLinks(janePatient);
assertLinkCount(0);
}
@ -367,7 +367,7 @@ public class EmpiMatchLinkSvcTest extends BaseEmpiR4Test {
//In a normal situation, janePatient2 would just match to jane patient, but here we need to hack it so they are their
//own individual Persons for the purpose of this test.
IAnyResource person = myPersonHelper.createSourceResourceFromEmpiTarget(janePatient2);
IAnyResource person = myPersonHelper.createGoldenResourceFromMdmTarget(janePatient2);
myEmpiLinkSvc.updateLink(person, janePatient2, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.AUTO, createContextForCreate("Patient"));
assertThat(janePatient, is(not(sameSourceResourceAs(janePatient2))));
@ -430,7 +430,7 @@ public class EmpiMatchLinkSvcTest extends BaseEmpiR4Test {
private SearchParameterMap buildGoldenRecordSearchParameterMap() {
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap.setLoadSynchronous(true);
searchParameterMap.add("_tag", new TokenParam(EmpiConstants.SYSTEM_EMPI_MANAGED, EmpiConstants.CODE_HAPI_EMPI_MANAGED));
searchParameterMap.add("_tag", new TokenParam(EmpiConstants.SYSTEM_MDM_MANAGED, EmpiConstants.CODE_HAPI_MDM_MANAGED));
return searchParameterMap;
}
@ -458,7 +458,7 @@ public class EmpiMatchLinkSvcTest extends BaseEmpiR4Test {
public void testCreateSourceResourceFromEmpiTarget() {
// Create Use Case #2 - adding patient with no EID
Patient janePatient = buildJanePatient();
Patient janeSourceResourcePatient = myPersonHelper.createSourceResourceFromEmpiTarget(janePatient);
Patient janeSourceResourcePatient = myPersonHelper.createGoldenResourceFromMdmTarget(janePatient);
// golden record now contains HAPI-generated EID and HAPI tag
assertTrue(EmpiUtil.isEmpiManaged(janeSourceResourcePatient));

View File

@ -90,13 +90,14 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
@Test
public void emptyMerge() {
assertEquals(2, getAllGoldenPatients().size());
assertEquals(2, getAllActiveGoldenPatients().size());
assertEquals(0, getAllRedirectedGoldenPatients().size());
Patient mergedGoldenPatient = mergeGoldenPatients();
assertEquals(myToGoldenPatient.getIdElement(), mergedGoldenPatient.getIdElement());
assertThat(mergedGoldenPatient, is(sameSourceResourceAs(mergedGoldenPatient)));
assertEquals(2, getAllGoldenPatients().size());
assertEquals(1, getAllActiveGoldenPatients().size());
assertEquals(1, getAllGoldenPatients().size());
assertEquals(1, getAllRedirectedGoldenPatients().size());
}
private Patient mergeGoldenPatients() {

View File

@ -25,8 +25,8 @@ public class EmpiConstants {
* TAG system for Person resources which are managed by HAPI EMPI.
*/
public static final String SYSTEM_EMPI_MANAGED = "https://hapifhir.org/NamingSystem/managing-empi-system";
public static final String CODE_HAPI_EMPI_MANAGED = "HAPI-EMPI";
public static final String SYSTEM_MDM_MANAGED = "https://hapifhir.org/NamingSystem/managing-mdm-system";
public static final String CODE_HAPI_MDM_MANAGED = "HAPI-MDM";
public static final String DISPLAY_HAPI_EMPI_MANAGED = "This Person can only be modified by Smile CDR's EMPI system.";
public static final String CODE_NO_EMPI_MANAGED = "NO-EMPI";
public static final String HAPI_ENTERPRISE_IDENTIFIER_SYSTEM = "http://hapifhir.io/fhir/NamingSystem/empi-person-enterprise-id";
@ -34,4 +34,9 @@ public class EmpiConstants {
public static final String FIHR_STRUCTURE_DEF_MATCH_GRADE_URL_NAMESPACE = "http://hl7.org/fhir/StructureDefinition/match-grade";
public static final String SYSTEM_GOLDEN_RECORD_STATUS = "http://hapifhir.io/fhir/NamingSystem/mdm-record-status";
public static final String CODE_GOLDEN_RECORD = "GOLDEN_RECORD";
public static final String CODE_GOLDEN_RECORD_REDIRECTED = "REDIRECTED";
public static final String DISPLAY_GOLDEN_RECORD = "Golden Record";
public static final String DISPLAY_GOLDEN_REDIRECT = "This resource was found to be a duplicate and has been redirected.";
}

View File

@ -86,7 +86,7 @@ public class EmpiControllerHelper {
throw new InvalidRequestException("Only Person resources can be merged. The " + theName + " points to a " + myFhirContext.getResourceType(thePerson));
}
if (!EmpiUtil.isEmpiManaged(thePerson)) {
throw new InvalidRequestException("Only EMPI managed resources can be merged. Empi managed resource have the " + EmpiConstants.CODE_HAPI_EMPI_MANAGED + " tag.");
throw new InvalidRequestException("Only EMPI managed resources can be merged. Empi managed resource have the " + EmpiConstants.CODE_HAPI_MDM_MANAGED + " tag.");
}
}
}

View File

@ -25,6 +25,9 @@ import ca.uhn.fhir.empi.api.EmpiConstants;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.annotation.Nonnull;
import java.util.Optional;
public final class EmpiUtil {
private EmpiUtil() {}
@ -45,7 +48,7 @@ public final class EmpiUtil {
* @return A boolean indicating whether EMPI should manage this resource.
*/
public static boolean isEmpiAccessible(IBaseResource theBaseResource) {
return theBaseResource.getMeta().getTag(EmpiConstants.SYSTEM_EMPI_MANAGED, EmpiConstants.CODE_NO_EMPI_MANAGED) == null;
return theBaseResource.getMeta().getTag(EmpiConstants.SYSTEM_MDM_MANAGED, EmpiConstants.CODE_NO_EMPI_MANAGED) == null;
}
/**
@ -56,9 +59,30 @@ public final class EmpiUtil {
* @return a boolean indicating whether or not EMPI manages this Person.
*/
public static boolean isEmpiManaged(IBaseResource theBaseResource) {
return theBaseResource.getMeta().getTag(EmpiConstants.SYSTEM_EMPI_MANAGED, EmpiConstants.CODE_HAPI_EMPI_MANAGED) != null;
return resourceHasTag(theBaseResource, EmpiConstants.SYSTEM_MDM_MANAGED, EmpiConstants.CODE_HAPI_MDM_MANAGED);
}
public static boolean isGoldenRecord(IBaseResource theBaseResource) {
return resourceHasTag(theBaseResource, EmpiConstants.SYSTEM_GOLDEN_RECORD_STATUS, EmpiConstants.CODE_GOLDEN_RECORD);
}
public static boolean isGoldenRecordRedirected(IBaseResource theBaseResource) {
return resourceHasTag(theBaseResource, EmpiConstants.SYSTEM_GOLDEN_RECORD_STATUS, EmpiConstants.CODE_GOLDEN_RECORD_REDIRECTED);
}
private static boolean resourceHasTag(IBaseResource theTheBaseResource, String theSystem, String theCode) {
return theTheBaseResource.getMeta().getTag(theSystem, theCode) != null;
}
private static Optional<? extends IBaseCoding> getTagWithSystem(IBaseResource theResource, String theSystem) {
return theResource.getMeta().getTag().stream().filter(tag -> tag.getSystem().equalsIgnoreCase(theSystem)).findFirst();
}
public static void removeTagWithSystem(IBaseResource theResource, String theSystem) {
theResource.getMeta().getTag().removeIf(tag -> tag.getSystem().equalsIgnoreCase(theSystem));
}
/**
* Sets the EMPI-managed tag, indicating the EMPI system has ownership of this
* Resource. No changes are made if resource is already maanged by EMPI.
@ -68,14 +92,37 @@ public final class EmpiUtil {
* Returns resource with the tag set.
*/
public static IBaseResource setEmpiManaged(IBaseResource theBaseResource) {
if (isEmpiManaged(theBaseResource)) {
return theBaseResource;
return setTagOnResource(theBaseResource, EmpiConstants.SYSTEM_MDM_MANAGED, EmpiConstants.CODE_HAPI_MDM_MANAGED, EmpiConstants.DISPLAY_HAPI_EMPI_MANAGED);
}
public static IBaseResource setGoldenResource(IBaseResource theBaseResource) {
return setTagOnResource(theBaseResource, EmpiConstants.SYSTEM_GOLDEN_RECORD_STATUS, EmpiConstants.CODE_GOLDEN_RECORD, EmpiConstants.DISPLAY_GOLDEN_RECORD);
}
public static IBaseResource setGoldenResourceRedirected(IBaseResource theBaseResource) {
return setTagOnResource(theBaseResource, EmpiConstants.SYSTEM_GOLDEN_RECORD_STATUS, EmpiConstants.CODE_GOLDEN_RECORD_REDIRECTED, EmpiConstants.DISPLAY_GOLDEN_REDIRECT);
}
/**
* WARNING: This code may _look_ like it replaces in place a code of a tag, but this DOES NOT ACTUALLY WORK!. In reality what will
* happen is a secondary tag will be created with the same system. the only way to actually remove a tag from a resource
* is by calling dao.removeTag(). This logic here is for the case where our representation of the resource still happens to contain
* a reference to a tag, to make sure it isn't double-added.
*/
@Nonnull
private static IBaseResource setTagOnResource(IBaseResource theGoldenResource, String theSystem, String theCode, String theDisplay) {
Optional<? extends IBaseCoding> tagWithSystem = getTagWithSystem(theGoldenResource, theSystem);
if (tagWithSystem.isPresent()) {
tagWithSystem.get().setCode(theCode);
tagWithSystem.get().setDisplay(theDisplay);
} else {
IBaseCoding tag = theGoldenResource.getMeta().addTag();
tag.setSystem(theSystem);
tag.setCode(theCode);
tag.setDisplay(theDisplay);
}
IBaseCoding tag = theBaseResource.getMeta().addTag();
tag.setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED);
tag.setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED);
tag.setDisplay(EmpiConstants.DISPLAY_HAPI_EMPI_MANAGED);
return theBaseResource;
return theGoldenResource;
}
public static boolean isEmpiManagedPerson(FhirContext theFhirContext, IBaseResource theResource) {

View File

@ -88,7 +88,7 @@ public class PersonHelper {
* @param <T> Supported MDM resource type (e.g. Patient, Practitioner)
* @param theIncomingResource The resource that will be used as the starting point for the MDM linking.
*/
public <T extends IAnyResource> T createSourceResourceFromEmpiTarget(T theIncomingResource) {
public <T extends IAnyResource> T createGoldenResourceFromMdmTarget(T theIncomingResource) {
validateContextSupported();
// get a ref to the actual ID Field
@ -102,26 +102,14 @@ public class PersonHelper {
addHapiEidIfNoExternalEidIsPresent(newSourceResource, sourceResourceIdentifier, theIncomingResource);
setActive(newSourceResource, resourceDefinition, true);
//setGoldenResource(newSourceResource, resourceDefinition, true);
EmpiUtil.setEmpiManaged(newSourceResource);
EmpiUtil.setGoldenResource(newSourceResource);
return (T) newSourceResource;
}
private void setActive(IBaseResource theResource, boolean theActiveFlag) {
setActive(theResource, myFhirContext.getResourceDefinition(theResource), theActiveFlag);
}
private void setActive(IBaseResource theNewSourceResource, RuntimeResourceDefinition theResourceDefinition, boolean theActiveFlag) {
BaseRuntimeChildDefinition activeChildDefinition = theResourceDefinition.getChildByName("active");
if (activeChildDefinition == null) {
ourLog.warn(String.format("Unable to set active flag on the provided source resource %s.", theNewSourceResource));
return;
}
activeChildDefinition.getMutator().setValue(theNewSourceResource, toBooleanType(theActiveFlag));
}
/**
* If there are no external EIDs on the incoming resource, create a new HAPI EID on the new SourceResource.
*/
@ -516,32 +504,10 @@ public class PersonHelper {
}
public void deactivateResource(IAnyResource theResource) {
// get a ref to the actual ID Field
setActive(theResource, myFhirContext.getResourceDefinition(theResource), false);
EmpiUtil.setGoldenResourceRedirected(theResource);
}
public boolean isDeactivated(IBaseResource thePerson) {
RuntimeResourceDefinition resourceDefinition = myFhirContext.getResourceDefinition(thePerson);
BaseRuntimeChildDefinition activeChildDefinition = resourceDefinition.getChildByName("active");
Optional<IBase> value = activeChildDefinition.getAccessor().getFirstValueOrNull(thePerson);
return value.map(v -> {
return !fromBooleanType(v);
}).orElseThrow(
() -> new UnsupportedOperationException(String.format("Resource %s does not support deactivation", resourceDefinition.getName()))
);
//
// }
// switch (myFhirContext.getVersion().getVersion()) {
// case R4:
// Person personR4 = (Person) thePerson;
// return !personR4.getActive();
// case DSTU3:
// org.hl7.fhir.dstu3.model.Person personStu3 = (org.hl7.fhir.dstu3.model.Person) thePerson;
// return !personStu3.getActive();
// default:
// throw
// }
public boolean isDeactivated(IBaseResource theGoldenResource) {
return EmpiUtil.isGoldenRecordRedirected(theGoldenResource);
}
}