Fix NPE in MDM post-mdm-clear on UPDATE operations. (#3851)
* Fix bug, imlpementation, changelog * wip * wip * wip * Add test * wip * wip
This commit is contained in:
parent
a676506fc5
commit
57f6a705a5
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
type: fix
|
||||||
|
issue: 3786
|
||||||
|
title: "Previously, when executing an update on a resource that had to undergo MDM, a nullpointer could occur. This has been fixed."
|
|
@ -19,7 +19,6 @@
|
||||||
*/
|
*/
|
||||||
package ca.uhn.fhir.jpa.mdm.svc;
|
package ca.uhn.fhir.jpa.mdm.svc;
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
|
||||||
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
|
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.candidate.MatchedGoldenResourceCandidate;
|
import ca.uhn.fhir.jpa.mdm.svc.candidate.MatchedGoldenResourceCandidate;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmGoldenResourceFindingSvc;
|
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmGoldenResourceFindingSvc;
|
||||||
|
@ -35,7 +34,6 @@ import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||||
import ca.uhn.fhir.mdm.util.EIDHelper;
|
import ca.uhn.fhir.mdm.util.EIDHelper;
|
||||||
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
|
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
|
||||||
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
@ -82,6 +80,7 @@ public class MdmEidUpdateService {
|
||||||
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(
|
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(
|
||||||
theTargetResource, updateContext.getMatchedGoldenResource(), theMdmTransactionContext);
|
theTargetResource, updateContext.getMatchedGoldenResource(), theMdmTransactionContext);
|
||||||
|
|
||||||
|
IAnyResource theOldGoldenResource = updateContext.getExistingGoldenResource();
|
||||||
if (updateContext.isRemainsMatchedToSameGoldenResource()) {
|
if (updateContext.isRemainsMatchedToSameGoldenResource()) {
|
||||||
// Copy over any new external EIDs which don't already exist.
|
// Copy over any new external EIDs which don't already exist.
|
||||||
if (!updateContext.isIncomingResourceHasAnEid() || updateContext.isHasEidsInCommon()) {
|
if (!updateContext.isIncomingResourceHasAnEid() || updateContext.isHasEidsInCommon()) {
|
||||||
|
@ -96,17 +95,24 @@ public class MdmEidUpdateService {
|
||||||
handleNoEidsInCommon(
|
handleNoEidsInCommon(
|
||||||
theTargetResource, theMatchedGoldenResourceCandidate, theMdmTransactionContext, updateContext);
|
theTargetResource, theMatchedGoldenResourceCandidate, theMdmTransactionContext, updateContext);
|
||||||
}
|
}
|
||||||
|
} else if (theOldGoldenResource == null) {
|
||||||
|
// If we are in an update, and there is no existing golden resource, it is likely due to a clear operation,
|
||||||
|
// and we need to start from scratch.
|
||||||
|
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(
|
||||||
|
theTargetResource, updateContext.getMatchedGoldenResource(), theMdmTransactionContext);
|
||||||
|
myMdmResourceDaoSvc.upsertGoldenResource(
|
||||||
|
updateContext.getMatchedGoldenResource(), theMdmTransactionContext.getResourceType());
|
||||||
|
myMdmLinkSvc.updateLink(
|
||||||
|
updateContext.getMatchedGoldenResource(),
|
||||||
|
theTargetResource,
|
||||||
|
theMatchedGoldenResourceCandidate.getMatchResult(),
|
||||||
|
MdmLinkSourceEnum.AUTO,
|
||||||
|
theMdmTransactionContext);
|
||||||
} else {
|
} else {
|
||||||
// This is a new linking scenario. we have to break the existing link and link to the new Golden Resource.
|
// This is a new linking scenario. we have to break the existing link and link to the new Golden Resource.
|
||||||
// For now, we create duplicate.
|
// For now, we create duplicate.
|
||||||
// updated patient has an EID that matches to a new candidate. Link them, and set the Golden Resources
|
// updated patient has an EID that matches to a new candidate. Link them, and set the Golden Resources
|
||||||
// possible duplicates
|
// possible duplicates
|
||||||
IAnyResource theOldGoldenResource = updateContext.getExistingGoldenResource();
|
|
||||||
if (theOldGoldenResource == null) {
|
|
||||||
throw new InternalErrorException(
|
|
||||||
Msg.code(2362)
|
|
||||||
+ "Old golden resource was null while updating MDM links with new golden resource. It is likely that a $mdm-clear was performed without a $mdm-submit. Link will not be updated.");
|
|
||||||
} else {
|
|
||||||
linkToNewGoldenResourceAndFlagAsDuplicate(
|
linkToNewGoldenResourceAndFlagAsDuplicate(
|
||||||
theTargetResource,
|
theTargetResource,
|
||||||
theMatchedGoldenResourceCandidate.getMatchResult(),
|
theMatchedGoldenResourceCandidate.getMatchResult(),
|
||||||
|
@ -120,7 +126,6 @@ public class MdmEidUpdateService {
|
||||||
updateContext.getMatchedGoldenResource(), theMdmTransactionContext.getResourceType());
|
updateContext.getMatchedGoldenResource(), theMdmTransactionContext.getResourceType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void handleNoEidsInCommon(
|
private void handleNoEidsInCommon(
|
||||||
IAnyResource theResource,
|
IAnyResource theResource,
|
||||||
|
@ -283,5 +288,9 @@ public class MdmEidUpdateService {
|
||||||
public boolean isRemainsMatchedToSameGoldenResource() {
|
public boolean isRemainsMatchedToSameGoldenResource() {
|
||||||
return myRemainsMatchedToSameGoldenResource;
|
return myRemainsMatchedToSameGoldenResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasExistingGoldenResource() {
|
||||||
|
return myExistingGoldenResource != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -626,6 +626,14 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
protected MdmTransactionContext buildUpdateResourceMdmTransactionContext() {
|
||||||
|
MdmTransactionContext retval = new MdmTransactionContext();
|
||||||
|
retval.setResourceType("Patient");
|
||||||
|
retval.setRestOperation(MdmTransactionContext.OperationType.UPDATE_RESOURCE);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
protected MdmLink createGoldenPatientAndLinkToSourcePatient(Long thePatientPid, MdmMatchResultEnum theMdmMatchResultEnum) {
|
protected MdmLink createGoldenPatientAndLinkToSourcePatient(Long thePatientPid, MdmMatchResultEnum theMdmMatchResultEnum) {
|
||||||
Patient patient = createPatient();
|
Patient patient = createPatient();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package ca.uhn.fhir.jpa.mdm.interceptor;
|
package ca.uhn.fhir.jpa.mdm.interceptor;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.batch2.api.StepExecutionDetails;
|
||||||
|
import ca.uhn.fhir.batch2.jobs.chunk.ResourceIdListWorkChunkJson;
|
||||||
|
import ca.uhn.fhir.batch2.model.JobInstance;
|
||||||
import ca.uhn.fhir.jpa.entity.MdmLink;
|
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||||
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
|
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
|
||||||
import ca.uhn.fhir.jpa.mdm.helper.MdmHelperConfig;
|
import ca.uhn.fhir.jpa.mdm.helper.MdmHelperConfig;
|
||||||
|
@ -7,7 +10,11 @@ import ca.uhn.fhir.jpa.mdm.helper.MdmHelperR4;
|
||||||
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkEvent;
|
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkEvent;
|
||||||
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkJson;
|
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkJson;
|
||||||
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
||||||
|
import ca.uhn.fhir.mdm.batch2.clear.MdmClearJobParameters;
|
||||||
|
import ca.uhn.fhir.mdm.batch2.clear.MdmClearStep;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||||
import ca.uhn.fhir.rest.server.messaging.ResourceOperationMessage;
|
import ca.uhn.fhir.rest.server.messaging.ResourceOperationMessage;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
@ -20,7 +27,11 @@ import org.springframework.data.domain.Example;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package ca.uhn.fhir.jpa.mdm.svc;
|
package ca.uhn.fhir.jpa.mdm.svc;
|
||||||
|
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
||||||
|
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
||||||
import ca.uhn.fhir.jpa.entity.MdmLink;
|
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||||
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
|
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
|
||||||
import ca.uhn.fhir.jpa.mdm.config.BaseTestMdmConfig;
|
import ca.uhn.fhir.jpa.mdm.config.BaseTestMdmConfig;
|
||||||
|
@ -80,7 +82,6 @@ public class MdmMatchLinkSvcTest {
|
||||||
|
|
||||||
private static final Logger ourLog = getLogger(MdmMatchLinkSvcTest.class);
|
private static final Logger ourLog = getLogger(MdmMatchLinkSvcTest.class);
|
||||||
|
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
public class NoBlockLinkTest extends BaseMdmR4Test {
|
public class NoBlockLinkTest extends BaseMdmR4Test {
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -231,6 +232,23 @@ public class MdmMatchLinkSvcTest {
|
||||||
assertLinksMatchVector(null, null, null);
|
assertLinksMatchVector(null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateMdmLinksForMdmSource_singleCandidateDuringUpdate_DoesNotNullPointer() {
|
||||||
|
|
||||||
|
//Given: A patient exists with a matched golden resource.
|
||||||
|
Patient jane = createPatientAndUpdateLinks(buildJanePatient());
|
||||||
|
Patient goldenJane = getGoldenResourceFromTargetResource(jane);
|
||||||
|
|
||||||
|
//When: A patient who has no existing MDM links comes in as an update
|
||||||
|
Patient secondaryJane = createPatient(buildJanePatient(), false, false);
|
||||||
|
secondaryJane.setActive(true);
|
||||||
|
IAnyResource resource = (IAnyResource) myPatientDao.update(secondaryJane).getResource();
|
||||||
|
|
||||||
|
//Then: The secondary jane should link to the first jane.
|
||||||
|
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(resource, buildUpdateResourceMdmTransactionContext());
|
||||||
|
assertThat(secondaryJane, is(sameGoldenResourceAs(jane)));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWhenPOSSIBLE_MATCHOccursOnGoldenResourceThatHasBeenManuallyNOMATCHedThatItIsBlocked() {
|
public void testWhenPOSSIBLE_MATCHOccursOnGoldenResourceThatHasBeenManuallyNOMATCHedThatItIsBlocked() {
|
||||||
Patient originalJane = createPatientAndUpdateLinks(buildJanePatient());
|
Patient originalJane = createPatientAndUpdateLinks(buildJanePatient());
|
||||||
|
|
Loading…
Reference in New Issue