4287 fix mdm golden resource merging (#4290)
* fixed goldenresourcemergersvcimpl to allow merging GoldenResource into SourceResource in PossibleDuplicate links * added changelog * adding some notations * working on tests * updated test to use new convention as demo * updated to allow creating patints too * updating more tests * updated all tests to use new notation * updates the tests * adding more test support * fix merge directions * fixed duplicate merging in * updates * cleanup * cleanup * fix changelog * changing how tests work * test cleanup * added a comment * fixing build * fix for multimap * fixing tests * minor change * addressed 2 points * review fixes * remove one change * adding todo Co-authored-by: leif stawnyczy <leifstawnyczy@leifs-mbp.home>
This commit is contained in:
parent
312128754b
commit
08ca687c72
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
issue: 4287
|
||||
type: fix
|
||||
title: "
|
||||
Fixed bug with merging 2 Golden Resources together.
|
||||
When merging GP1 into GP2, the result was (incorrectly)
|
||||
GP2 REDIRECT GP1 (instead of GP1 REDIRECT GP2).
|
||||
"
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
issue: 4287
|
||||
type: fix
|
||||
title: "
|
||||
Fixed bug with merging 2 Golden Resources together.
|
||||
When merging a POSSIBLE_DUPLICATE of GP1 to GP2,
|
||||
or GP2 to GP1, depending on the order, a dangling
|
||||
self-referential link would sometimes remain
|
||||
(eg, GP1 POSSIBLE_MATCH GP1).
|
||||
"
|
|
@ -93,7 +93,9 @@ public class MdmLinkDaoSvc {
|
|||
}
|
||||
|
||||
@Nonnull
|
||||
public IMdmLink getOrCreateMdmLinkByGoldenResourceAndSourceResource(IAnyResource theGoldenResource, IAnyResource theSourceResource) {
|
||||
public IMdmLink getOrCreateMdmLinkByGoldenResourceAndSourceResource(
|
||||
IAnyResource theGoldenResource, IAnyResource theSourceResource
|
||||
) {
|
||||
ResourcePersistentId goldenResourcePid = myIdHelperService.getPidOrNull(RequestPartitionId.allPartitions(), theGoldenResource);
|
||||
ResourcePersistentId sourceResourcePid = myIdHelperService.getPidOrNull(RequestPartitionId.allPartitions(), theSourceResource);
|
||||
Optional<? extends IMdmLink> oExisting = getLinkByGoldenResourcePidAndSourceResourcePid(goldenResourcePid, sourceResourcePid);
|
||||
|
@ -125,6 +127,7 @@ public class MdmLinkDaoSvc {
|
|||
* @param theSourceResourcePid The ResourcepersistenceId of the Source resource
|
||||
* @return The {@link IMdmLink} entity that matches these criteria if exists
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Optional<? extends IMdmLink> getLinkByGoldenResourcePidAndSourceResourcePid(ResourcePersistentId theGoldenResourcePid, ResourcePersistentId theSourceResourcePid) {
|
||||
if (theSourceResourcePid == null || theGoldenResourcePid == null) {
|
||||
return Optional.empty();
|
||||
|
@ -132,7 +135,10 @@ public class MdmLinkDaoSvc {
|
|||
IMdmLink link = myMdmLinkFactory.newMdmLink();
|
||||
link.setSourcePersistenceId(theSourceResourcePid);
|
||||
link.setGoldenResourcePersistenceId(theGoldenResourcePid);
|
||||
|
||||
//TODO - replace the use of example search
|
||||
Example<? extends IMdmLink> example = Example.of(link);
|
||||
|
||||
return myMdmLinkDao.findOne(example);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,13 +23,13 @@ package ca.uhn.fhir.jpa.mdm.svc;
|
|||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.mdm.util.MdmPartitionHelper;
|
||||
import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
|
||||
import ca.uhn.fhir.mdm.api.IMdmLink;
|
||||
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
|
||||
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
||||
import ca.uhn.fhir.mdm.log.Logs;
|
||||
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||
|
@ -95,7 +95,7 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
|
|||
mergeGoldenResourceLinks(theFromGoldenResource, theToGoldenResource, theFromGoldenResource.getIdElement(), theMdmTransactionContext);
|
||||
|
||||
//Create the new REDIRECT link
|
||||
addMergeLink(theToGoldenResource, theFromGoldenResource, resourceType);
|
||||
addMergeLink(theToGoldenResource, theFromGoldenResource, resourceType, theMdmTransactionContext);
|
||||
|
||||
//Strip the golden resource tag from the now-deprecated resource.
|
||||
myMdmResourceDaoSvc.removeGoldenResourceTag(theFromGoldenResource, resourceType);
|
||||
|
@ -111,25 +111,54 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
|
|||
return theToGoldenResource;
|
||||
}
|
||||
|
||||
private void addMergeLink(IAnyResource theGoldenResource, IAnyResource theTargetResource, String theResourceType) {
|
||||
IMdmLink mdmLink = myMdmLinkDaoSvc
|
||||
.getOrCreateMdmLinkByGoldenResourceAndSourceResource(theGoldenResource, theTargetResource);
|
||||
/**
|
||||
* This connects 2 golden resources (GR and TR here)
|
||||
*
|
||||
* 1 Deletes any current links: TR, ?, ?, GR
|
||||
* 2 Creates a new link: GR, MANUAL, REDIRECT, TR
|
||||
*
|
||||
* Before:
|
||||
* TR -> GR
|
||||
*
|
||||
* After:
|
||||
* GR -> TR
|
||||
*/
|
||||
private void addMergeLink(
|
||||
IAnyResource theGoldenResource,
|
||||
IAnyResource theTargetResource,
|
||||
String theResourceType,
|
||||
MdmTransactionContext theMdmTransactionContext
|
||||
) {
|
||||
myMdmLinkSvc.deleteLink(theGoldenResource, theTargetResource,
|
||||
theMdmTransactionContext);
|
||||
|
||||
mdmLink
|
||||
.setMdmSourceType(theResourceType)
|
||||
.setMatchResult(MdmMatchResultEnum.REDIRECT)
|
||||
.setLinkSource(MdmLinkSourceEnum.MANUAL);
|
||||
myMdmLinkDaoSvc.save(mdmLink);
|
||||
myMdmLinkDaoSvc.createOrUpdateLinkEntity(
|
||||
theTargetResource, // golden / LHS
|
||||
theGoldenResource, // source / RHS
|
||||
new MdmMatchOutcome(null, null).setMatchResultEnum(MdmMatchResultEnum.REDIRECT),
|
||||
MdmLinkSourceEnum.MANUAL,
|
||||
theMdmTransactionContext // mdm transaction context
|
||||
);
|
||||
}
|
||||
|
||||
private RequestPartitionId getPartitionIdForResource(IAnyResource theResource) {
|
||||
RequestPartitionId partitionId = (RequestPartitionId) theResource.getUserData(Constants.RESOURCE_PARTITION_ID);
|
||||
// TODO - this seems to be null on the put with
|
||||
// client id (forced id). Is this a bug?
|
||||
if (partitionId == null) {
|
||||
partitionId = RequestPartitionId.allPartitions();
|
||||
}
|
||||
return partitionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method which performs merger of links between resources, and cleans up dangling links afterwards.
|
||||
* <p>
|
||||
* 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.
|
||||
* 2.a If the link does not exist on the TO resource, redirect the link from the FROM resource to the TO resource
|
||||
* 2.b If the link does not exist on the TO resource, but is actually self-referential, it will just be removed
|
||||
* 3. If an incoming link is MANUAL, and there's 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
|
||||
|
@ -137,12 +166,26 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
|
|||
* @param theToResourcePid
|
||||
* @param theMdmTransactionContext
|
||||
*/
|
||||
private void mergeGoldenResourceLinks(IAnyResource theFromResource, IAnyResource theToResource, IIdType theToResourcePid, MdmTransactionContext theMdmTransactionContext) {
|
||||
List<? extends IMdmLink> fromLinks = myMdmLinkDaoSvc.findMdmLinksByGoldenResource(theFromResource); // fromLinks - links going to theFromResource
|
||||
List<? extends IMdmLink> toLinks = myMdmLinkDaoSvc.findMdmLinksByGoldenResource(theToResource); // toLinks - links going to theToResource
|
||||
private void mergeGoldenResourceLinks(
|
||||
IAnyResource theFromResource,
|
||||
IAnyResource theToResource,
|
||||
IIdType theToResourcePid,
|
||||
MdmTransactionContext theMdmTransactionContext
|
||||
) {
|
||||
// fromLinks - links from theFromResource to any resource
|
||||
List<? extends IMdmLink> fromLinks = myMdmLinkDaoSvc.findMdmLinksByGoldenResource(theFromResource);
|
||||
// toLinks - links from theToResource to any resource
|
||||
List<? extends IMdmLink> toLinks = myMdmLinkDaoSvc.findMdmLinksByGoldenResource(theToResource);
|
||||
List<IMdmLink> toDelete = new ArrayList<>();
|
||||
ResourcePersistentId goldenResourcePid = myIdHelperService.resolveResourcePersistentIds((RequestPartitionId) theToResource.getUserData(Constants.RESOURCE_PARTITION_ID), theToResource.getIdElement().getResourceType(), theToResource.getIdElement().getIdPart());
|
||||
|
||||
ResourcePersistentId goldenResourcePid = myIdHelperService.resolveResourcePersistentIds(
|
||||
getPartitionIdForResource(theToResource),
|
||||
theToResource.getIdElement().getResourceType(),
|
||||
theToResource.getIdElement().getIdPart()
|
||||
);
|
||||
|
||||
// reassign links:
|
||||
// to <- from
|
||||
for (IMdmLink fromLink : fromLinks) {
|
||||
Optional<? extends IMdmLink> optionalToLink = findFirstLinkWithMatchingSource(toLinks, fromLink);
|
||||
if (optionalToLink.isPresent()) {
|
||||
|
@ -162,17 +205,25 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
//1
|
||||
// 1
|
||||
toDelete.add(fromLink);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
//2 The original TO links didn't contain this target, so move it over to the toGoldenResource
|
||||
fromLink.setGoldenResourcePersistenceId(goldenResourcePid);
|
||||
ourLog.trace("Saving link {}", fromLink);
|
||||
myMdmLinkDaoSvc.save(fromLink);
|
||||
|
||||
if (fromLink.getSourcePersistenceId().equals(goldenResourcePid)) {
|
||||
// 2.b if the link is going to be self-referential we'll just delete it
|
||||
// (ie, do not link back to itself)
|
||||
myMdmLinkDaoSvc.deleteLink(fromLink);
|
||||
} else {
|
||||
// 2.a The original TO links didn't contain this target, so move it over to the toGoldenResource.
|
||||
fromLink.setGoldenResourcePersistenceId(goldenResourcePid);
|
||||
ourLog.trace("Saving link {}", fromLink);
|
||||
myMdmLinkDaoSvc.save(fromLink);
|
||||
}
|
||||
}
|
||||
//1 Delete dangling links
|
||||
|
||||
// 1 Delete dangling links
|
||||
toDelete.forEach(link -> myMdmLinkDaoSvc.deleteLink(link));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,26 +1,64 @@
|
|||
package ca.uhn.fhir.jpa.mdm.helper;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.data.IMdmLinkJpaRepository;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
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.dao.MdmLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.mdm.helper.testmodels.MDMLinkResults;
|
||||
import ca.uhn.fhir.jpa.mdm.helper.testmodels.MDMState;
|
||||
import ca.uhn.fhir.jpa.mdm.helper.testmodels.MdmTestLinkExpression;
|
||||
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
|
||||
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
||||
import ca.uhn.fhir.mdm.dao.IMdmLinkDao;
|
||||
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||
import org.hl7.fhir.r4.model.Identifier;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Service
|
||||
public class MdmLinkHelper {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(MdmLinkHelper.class);
|
||||
|
||||
private enum Side {
|
||||
LHS, // left hand side; practically speaking, this is the GoldenResource of the link
|
||||
RHS // right hand side; practically speaking, this is the SourceResource of the link
|
||||
}
|
||||
|
||||
@Autowired
|
||||
IMdmLinkJpaRepository myMdmLinkDao;
|
||||
private IMdmLinkDao myMdmLinkRepo;
|
||||
@Autowired
|
||||
private IFhirResourceDao<Patient> myPatientDao;
|
||||
@Autowired
|
||||
private MdmLinkDaoSvc myMdmLinkDaoSvc;
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Autowired
|
||||
private IMdmLinkDao myMdmLinkDao;
|
||||
@Autowired
|
||||
private IdHelperService myIdHelperService;
|
||||
|
||||
@Transactional
|
||||
public void logMdmLinks() {
|
||||
List<MdmLink> links = myMdmLinkDao.findAll();
|
||||
List<MdmLink> links = myMdmLinkRepo.findAll();
|
||||
ourLog.info("All MDM Links:");
|
||||
for (MdmLink link : links) {
|
||||
IdDt goldenResourceId = link.getGoldenResource().getIdDt().toVersionless();
|
||||
|
@ -28,4 +66,204 @@ public class MdmLinkHelper {
|
|||
ourLog.info("{}: {}, {}, {}, {}", link.getId(), goldenResourceId, targetId, link.getMatchResult(), link.getLinkSource());
|
||||
}
|
||||
}
|
||||
|
||||
private MdmTransactionContext createContextForCreate(String theResourceType) {
|
||||
MdmTransactionContext ctx = new MdmTransactionContext();
|
||||
ctx.setRestOperation(MdmTransactionContext.OperationType.CREATE_RESOURCE);
|
||||
ctx.setResourceType(theResourceType);
|
||||
ctx.setTransactionLogMessages(null);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates all the initial links specified in the state object.
|
||||
*
|
||||
* These links will be returned in an MDMLinkResults object, in case
|
||||
* they are needed.
|
||||
*/
|
||||
public MDMLinkResults setup(MDMState<Patient> theState) {
|
||||
MDMLinkResults results = new MDMLinkResults();
|
||||
|
||||
List<MdmTestLinkExpression> inputExpressions = theState.getParsedInputState();
|
||||
|
||||
// create all patients if needed
|
||||
for (MdmTestLinkExpression inputExpression : inputExpressions) {
|
||||
createIfNeeded(theState, inputExpression.getLeftSideResourceIdentifier());
|
||||
createIfNeeded(theState, inputExpression.getRightSideResourceIdentifier());
|
||||
}
|
||||
|
||||
// create all the links
|
||||
for (MdmTestLinkExpression inputExpression : theState.getParsedInputState()) {
|
||||
ourLog.info(inputExpression.getLinkExpression());
|
||||
|
||||
Patient goldenResource = theState.getParameter(inputExpression.getLeftSideResourceIdentifier());
|
||||
Patient targetResource = theState.getParameter(inputExpression.getRightSideResourceIdentifier());
|
||||
|
||||
MdmLinkSourceEnum matchSourceType = MdmLinkSourceEnum.valueOf(inputExpression.getMdmLinkSource());
|
||||
MdmMatchResultEnum matchResultType = MdmMatchResultEnum.valueOf(inputExpression.getMdmMatchResult());
|
||||
|
||||
MdmMatchOutcome matchOutcome = new MdmMatchOutcome(
|
||||
null,
|
||||
null
|
||||
);
|
||||
matchOutcome.setMatchResultEnum(matchResultType);
|
||||
|
||||
MdmLink link = (MdmLink) myMdmLinkDaoSvc.createOrUpdateLinkEntity(
|
||||
goldenResource, // golden
|
||||
targetResource, // source
|
||||
matchOutcome, // match outcome
|
||||
matchSourceType, // link source
|
||||
createContextForCreate("Patient") // context
|
||||
);
|
||||
|
||||
results.addResult(link);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private void createIfNeeded(MDMState<Patient> theState, String thePatientId) {
|
||||
Patient patient = theState.getParameter(thePatientId);
|
||||
if (patient == null) {
|
||||
// if it doesn't exist, create it
|
||||
patient = createPatientAndTags(thePatientId, theState);
|
||||
theState.addParameter(thePatientId, patient);
|
||||
}
|
||||
}
|
||||
|
||||
private Patient createPatientAndTags(String theId, MDMState<Patient> theState) {
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true); // all mdm patients must be active
|
||||
|
||||
// we add an identifier and use a forced id
|
||||
// to make test debugging a little simpler
|
||||
patient.addIdentifier(new Identifier().setValue(theId));
|
||||
patient.setId(theId);
|
||||
|
||||
// Golden patients will be "GP#"
|
||||
if (theId.length() >= 2 && theId.charAt(0) == 'G') {
|
||||
// golden resource
|
||||
MdmResourceUtil.setGoldenResource(patient);
|
||||
}
|
||||
MdmResourceUtil.setMdmManaged(patient);
|
||||
|
||||
DaoMethodOutcome outcome = myPatientDao.update(patient,
|
||||
SystemRequestDetails.forAllPartitions());
|
||||
Patient outputPatient = (Patient) outcome.getResource();
|
||||
theState.addPID(theId, outcome.getPersistentId());
|
||||
return outputPatient;
|
||||
}
|
||||
|
||||
public void validateResults(MDMState<Patient> theState) {
|
||||
List<MdmTestLinkExpression> expectedOutputStates = theState.getParsedOutputState();
|
||||
|
||||
StringBuilder outputStateSB = new StringBuilder();
|
||||
|
||||
// for every parameter, we'll get all links
|
||||
for (Map.Entry<String, Patient> entrySet : theState.getParameterToValue().entrySet()) {
|
||||
Patient patient = entrySet.getValue();
|
||||
List<MdmLink> links = getAllMdmLinks(patient);
|
||||
for (MdmLink link : links) {
|
||||
if (!outputStateSB.isEmpty()) {
|
||||
outputStateSB.append("\n");
|
||||
}
|
||||
outputStateSB.append(createStateFromLink(link, theState));
|
||||
theState.addLinksForResource(patient, link);
|
||||
}
|
||||
}
|
||||
|
||||
String actualOutputState = outputStateSB.toString();
|
||||
ourLog.info("Expected: \n" + theState.getOutputState());
|
||||
ourLog.info("Actual: \n" + actualOutputState);
|
||||
|
||||
int totalExpectedLinks = expectedOutputStates.size();
|
||||
int totalActualLinks = theState.getActualOutcomeLinks().entries().size();
|
||||
|
||||
assertEquals(totalExpectedLinks, totalActualLinks,
|
||||
String.format("Invalid number of links. Expected %d, Actual %d.",
|
||||
totalExpectedLinks, totalActualLinks)
|
||||
);
|
||||
|
||||
for (MdmTestLinkExpression stateExpression : expectedOutputStates) {
|
||||
ourLog.info(stateExpression.getLinkExpression());
|
||||
|
||||
Patient leftSideResource = theState.getParameter(stateExpression.getLeftSideResourceIdentifier());
|
||||
Collection<MdmLink> links = theState.getActualOutcomeLinks().get(leftSideResource);
|
||||
assertFalse(links.isEmpty(), String.format("No links found, but expected state: %s", stateExpression));
|
||||
|
||||
MdmLinkSourceEnum matchSourceType = MdmLinkSourceEnum.valueOf(stateExpression.getMdmLinkSource());
|
||||
MdmMatchResultEnum matchResultType = MdmMatchResultEnum.valueOf(stateExpression.getMdmMatchResult());
|
||||
|
||||
Patient rightSideResource = theState.getParameter(stateExpression.getRightSideResourceIdentifier());
|
||||
|
||||
boolean foundLink = false;
|
||||
for (MdmLink link : links) {
|
||||
if (isResourcePartOfLink(link, leftSideResource, Side.LHS, theState)
|
||||
&& isResourcePartOfLink(link, rightSideResource, Side.RHS, theState)
|
||||
&& link.getMatchResult() == matchResultType
|
||||
&& link.getLinkSource() == matchSourceType
|
||||
) {
|
||||
foundLink = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(foundLink, String.format("State: %s - not found", stateExpression));
|
||||
}
|
||||
}
|
||||
|
||||
public List<MdmLink> getAllMdmLinks(Patient theGoldenPatient) {
|
||||
return myMdmLinkDaoSvc.findMdmLinksByGoldenResource(theGoldenPatient).stream()
|
||||
.map( link -> (MdmLink) link)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean isResourcePartOfLink(
|
||||
MdmLink theLink,
|
||||
Patient theResource,
|
||||
Side theSide,
|
||||
MDMState<Patient> theState
|
||||
) {
|
||||
ResourcePersistentId resourcePid = theState.getPID(theResource.getIdElement().getIdPart());
|
||||
|
||||
long linkPid;
|
||||
if (theSide == Side.LHS) {
|
||||
// LHS
|
||||
linkPid = theLink.getGoldenResourcePid();
|
||||
} else {
|
||||
// RHS
|
||||
linkPid = theLink.getSourcePid();
|
||||
}
|
||||
|
||||
return linkPid == resourcePid.getIdAsLong();
|
||||
}
|
||||
|
||||
private String createStateFromLink(MdmLink theLink, MDMState<Patient> theState) {
|
||||
String LHS = "";
|
||||
String RHS = "";
|
||||
for (Map.Entry<String, Patient> set : theState.getParameterToValue().entrySet()) {
|
||||
Patient patient = set.getValue();
|
||||
if (isResourcePartOfLink(theLink, patient, Side.LHS, theState)) {
|
||||
LHS = set.getKey();
|
||||
}
|
||||
if (isResourcePartOfLink(theLink, patient, Side.RHS, theState)) {
|
||||
RHS = set.getKey();
|
||||
}
|
||||
|
||||
if (isNotBlank(LHS) && isNotBlank(RHS)) {
|
||||
boolean selfReferential = LHS.equals(RHS);
|
||||
|
||||
String link = LHS + ", "
|
||||
+ theLink.getLinkSource().name() + ", "
|
||||
+ theLink.getMatchResult().name() + ", "
|
||||
+ RHS;
|
||||
if (selfReferential) {
|
||||
link += " <- Invalid Self Referencing link!";
|
||||
}
|
||||
return link;
|
||||
}
|
||||
}
|
||||
|
||||
return "INVALID LINK: " + theLink.getId().toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package ca.uhn.fhir.jpa.mdm.helper.testmodels;
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MDMLinkResults {
|
||||
|
||||
private List<MdmLink> myResults;
|
||||
|
||||
public List<MdmLink> getResults() {
|
||||
if (myResults == null) {
|
||||
myResults = new ArrayList<>();
|
||||
}
|
||||
return myResults;
|
||||
}
|
||||
|
||||
public MDMLinkResults addResult(MdmLink theLink) {
|
||||
getResults().add(theLink);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setResults(List<MdmLink> theResults) {
|
||||
myResults = theResults;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
package ca.uhn.fhir.jpa.mdm.helper.testmodels;
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||
import org.testcontainers.shaded.com.google.common.collect.HashMultimap;
|
||||
import org.testcontainers.shaded.com.google.common.collect.Multimap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MDMState<T> {
|
||||
/**
|
||||
* Collection of Parameter keys -> values to use
|
||||
*
|
||||
* Eg:
|
||||
* P1 -> (some patient resource)
|
||||
* PG -> (some golden patient resource)
|
||||
*/
|
||||
private Map<String, T> myParameterToValue;
|
||||
|
||||
/**
|
||||
* Initial state for test.
|
||||
* Comma separated lines with:
|
||||
* Left param value, MdmLinkSourceEnum value, MdmMatchResultEnum value, Right param value
|
||||
*
|
||||
* Each input line represents a link state
|
||||
*
|
||||
* Eg:
|
||||
* PG1, MANUAL, MATCH, P1
|
||||
* PG1, AUTO, POSSIBLE_MATCH, P2
|
||||
*/
|
||||
private String myInputState;
|
||||
|
||||
/**
|
||||
* A list of link expressions for input state.
|
||||
*/
|
||||
private List<MdmTestLinkExpression> myInputLinks;
|
||||
|
||||
/**
|
||||
* Output state to verify at end of test.
|
||||
* Same format as input
|
||||
*/
|
||||
private String myOutputState;
|
||||
|
||||
/**
|
||||
* A list of link expressions for output state.
|
||||
*/
|
||||
private List<MdmTestLinkExpression> myOutputLinks;
|
||||
|
||||
/**
|
||||
* The actual outcome links.
|
||||
* Resource (typically a golden resource) -> Link
|
||||
*/
|
||||
private Multimap<T, MdmLink> myActualOutcomeLinks;
|
||||
|
||||
/**
|
||||
* Map of forcedId -> resource persistent id for each resource created
|
||||
*/
|
||||
private final Map<String, ResourcePersistentId> myForcedIdToPID = new HashMap<>();
|
||||
|
||||
public void addPID(String theForcedId, ResourcePersistentId thePid) {
|
||||
assert !myForcedIdToPID.containsKey(theForcedId);
|
||||
myForcedIdToPID.put(theForcedId, thePid);
|
||||
}
|
||||
|
||||
public ResourcePersistentId getPID(String theForcedId) {
|
||||
return myForcedIdToPID.get(theForcedId);
|
||||
}
|
||||
|
||||
public Multimap<T, MdmLink> getActualOutcomeLinks() {
|
||||
if (myActualOutcomeLinks == null) {
|
||||
myActualOutcomeLinks = HashMultimap.create();
|
||||
}
|
||||
return myActualOutcomeLinks;
|
||||
}
|
||||
|
||||
public MDMState<T> addLinksForResource(T theResource, MdmLink... theLinks) {
|
||||
for (MdmLink link : theLinks) {
|
||||
getActualOutcomeLinks().put(theResource, link);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setActualOutcomeLinks(Multimap<T, MdmLink> theActualOutcomeLinks) {
|
||||
myActualOutcomeLinks = theActualOutcomeLinks;
|
||||
}
|
||||
|
||||
public Map<String, T> getParameterToValue() {
|
||||
if (myParameterToValue == null) {
|
||||
myParameterToValue = new HashMap<>();
|
||||
}
|
||||
return myParameterToValue;
|
||||
}
|
||||
|
||||
public T getParameter(String theKey) {
|
||||
return getParameterToValue().get(theKey);
|
||||
}
|
||||
|
||||
public MDMState<T> addParameter(String myParamKey, T myValue) {
|
||||
getParameterToValue().put(myParamKey, myValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setParameterToValue(Map<String, T> theParameterToValue) {
|
||||
myParameterToValue = theParameterToValue;
|
||||
}
|
||||
|
||||
public String getInputState() {
|
||||
return myInputState;
|
||||
}
|
||||
|
||||
public MDMState<T> setInputState(String theInputState) {
|
||||
myInputState = theInputState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getOutputState() {
|
||||
return myOutputState;
|
||||
}
|
||||
|
||||
public List<MdmTestLinkExpression> getParsedInputState() {
|
||||
if (myInputLinks == null) {
|
||||
myInputLinks = new ArrayList<>();
|
||||
|
||||
String[] inputState = myInputState.split("\n");
|
||||
for (String is : inputState) {
|
||||
myInputLinks.add(new MdmTestLinkExpression(is));
|
||||
}
|
||||
}
|
||||
return myInputLinks;
|
||||
}
|
||||
|
||||
public List<MdmTestLinkExpression> getParsedOutputState() {
|
||||
if (myOutputLinks == null) {
|
||||
myOutputLinks = new ArrayList<>();
|
||||
String[] states = myOutputState.split("\n");
|
||||
for (String os : states) {
|
||||
myOutputLinks.add(new MdmTestLinkExpression(os));
|
||||
}
|
||||
}
|
||||
return myOutputLinks;
|
||||
}
|
||||
|
||||
public MDMState<T> setOutputState(String theOutputState) {
|
||||
myOutputState = theOutputState;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package ca.uhn.fhir.jpa.mdm.helper.testmodels;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class MdmTestLinkExpression {
|
||||
|
||||
private final String myLinkExpression;
|
||||
|
||||
private final String myLeftSideResourceIdentifier;
|
||||
private final String myMdmLinkSource;
|
||||
private final String myMdmMatchResult;
|
||||
private final String myRightSideResourceIdentifier;
|
||||
|
||||
public MdmTestLinkExpression(
|
||||
String theInputState
|
||||
) {
|
||||
myLinkExpression = theInputState;
|
||||
String[] params = parseState(theInputState);
|
||||
|
||||
myLeftSideResourceIdentifier = params[0].trim();
|
||||
myMdmLinkSource = params[1].trim();
|
||||
myMdmMatchResult = params[2].trim();
|
||||
myRightSideResourceIdentifier = params[3].trim();
|
||||
}
|
||||
|
||||
private String[] parseState(String theStateString) {
|
||||
String[] state = theStateString.split(",");
|
||||
if (state.length != 4) {
|
||||
// we're using this exclusively in test area
|
||||
fail(
|
||||
String.format("%s must contain 4 arguments; found %d", theStateString, state.length)
|
||||
);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public String getLinkExpression() {
|
||||
return myLinkExpression;
|
||||
}
|
||||
|
||||
public String getLeftSideResourceIdentifier() {
|
||||
return myLeftSideResourceIdentifier;
|
||||
}
|
||||
|
||||
public String getMdmLinkSource() {
|
||||
return myMdmLinkSource;
|
||||
}
|
||||
|
||||
public String getMdmMatchResult() {
|
||||
return myMdmMatchResult;
|
||||
}
|
||||
|
||||
public String getRightSideResourceIdentifier() {
|
||||
return myRightSideResourceIdentifier;
|
||||
}
|
||||
}
|
|
@ -76,8 +76,10 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
|
|||
|
||||
@Test
|
||||
public void testMerge() {
|
||||
Patient mergedSourcePatient = (Patient) myMdmProvider.mergeGoldenResources(myFromGoldenPatientId,
|
||||
myToGoldenPatientId, null, myRequestDetails);
|
||||
Patient mergedSourcePatient = (Patient) myMdmProvider.mergeGoldenResources(
|
||||
myFromGoldenPatientId, // from
|
||||
myToGoldenPatientId, // to
|
||||
null, myRequestDetails);
|
||||
|
||||
// we do not check setActive anymore - as not all types support that
|
||||
assertTrue(MdmResourceUtil.isGoldenRecord(mergedSourcePatient));
|
||||
|
@ -97,12 +99,12 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
|
|||
// Optional<Identifier> redirect = fromSourcePatient.getIdentifier().stream().filter(theIdentifier -> theIdentifier.getSystem().equals("REDIRECT")).findFirst();
|
||||
// assertThat(redirect.get().getValue(), is(equalTo(myToSourcePatient.getIdElement().toUnqualified().getValue())));
|
||||
|
||||
List<MdmLink> links = (List<MdmLink>) myMdmLinkDaoSvc.findMdmLinksBySourceResource(myFromGoldenPatient);
|
||||
List<MdmLink> links = (List<MdmLink>) myMdmLinkDaoSvc.findMdmLinksBySourceResource(myToGoldenPatient);
|
||||
assertThat(links, hasSize(1));
|
||||
|
||||
MdmLink link = links.get(0);
|
||||
assertEquals(link.getSourcePid(), myFromGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getGoldenResourcePid(), myToGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getSourcePid(), myToGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getGoldenResourcePid(), myFromGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getMatchResult(), MdmMatchResultEnum.REDIRECT);
|
||||
assertEquals(link.getLinkSource(), MdmLinkSourceEnum.MANUAL);
|
||||
}
|
||||
|
@ -117,8 +119,11 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
|
|||
Patient toGoldenPatient = createPatientOnPartition(new Patient(), true, false, requestPartitionId);
|
||||
StringType toGoldenPatientId = new StringType(toGoldenPatient.getIdElement().getValue());
|
||||
|
||||
Patient mergedSourcePatient = (Patient) myMdmProvider.mergeGoldenResources(fromGoldenPatientId,
|
||||
toGoldenPatientId, null, myRequestDetails);
|
||||
// test
|
||||
Patient mergedSourcePatient = (Patient) myMdmProvider.mergeGoldenResources(
|
||||
fromGoldenPatientId,
|
||||
toGoldenPatientId,
|
||||
null, myRequestDetails);
|
||||
|
||||
assertTrue(MdmResourceUtil.isGoldenRecord(mergedSourcePatient));
|
||||
assertFalse(MdmResourceUtil.isGoldenRecordRedirected(mergedSourcePatient));
|
||||
|
@ -129,12 +134,12 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
|
|||
// 2 from the set-up and only one from this test should be golden resource
|
||||
assertEquals(3, getAllGoldenPatients().size());
|
||||
|
||||
List<MdmLink> links = (List<MdmLink>) myMdmLinkDaoSvc.findMdmLinksBySourceResource(fromGoldenPatient);
|
||||
List<MdmLink> links = (List<MdmLink>) myMdmLinkDaoSvc.findMdmLinksBySourceResource(toGoldenPatient);
|
||||
assertThat(links, hasSize(1));
|
||||
|
||||
MdmLink link = links.get(0);
|
||||
assertEquals(link.getSourcePid(), fromGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getGoldenResourcePid(), toGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getSourcePid(), toGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getGoldenResourcePid(), fromGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getMatchResult(), MdmMatchResultEnum.REDIRECT);
|
||||
assertEquals(link.getLinkSource(), MdmLinkSourceEnum.MANUAL);
|
||||
}
|
||||
|
@ -164,8 +169,10 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
|
|||
Patient patient = TerserUtil.clone(myFhirContext, myFromGoldenPatient);
|
||||
patient.setIdElement(null);
|
||||
|
||||
Patient mergedSourcePatient = (Patient) myMdmProvider.mergeGoldenResources(myFromGoldenPatientId,
|
||||
myToGoldenPatientId, patient, myRequestDetails);
|
||||
Patient mergedSourcePatient = (Patient) myMdmProvider.mergeGoldenResources(
|
||||
myFromGoldenPatientId, // from
|
||||
myToGoldenPatientId, // to
|
||||
patient, myRequestDetails);
|
||||
|
||||
assertEquals(myToGoldenPatient.getIdElement(), mergedSourcePatient.getIdElement());
|
||||
assertThat(mergedSourcePatient, is(sameGoldenResourceAs(myToGoldenPatient)));
|
||||
|
@ -176,12 +183,12 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
|
|||
assertFalse(MdmResourceUtil.isGoldenRecord(fromSourcePatient));
|
||||
assertTrue(MdmResourceUtil.isGoldenRecordRedirected(fromSourcePatient));
|
||||
|
||||
List<MdmLink> links = (List<MdmLink>) myMdmLinkDaoSvc.findMdmLinksBySourceResource(myFromGoldenPatient);
|
||||
List<MdmLink> links = (List<MdmLink>) myMdmLinkDaoSvc.findMdmLinksBySourceResource(myToGoldenPatient);
|
||||
assertThat(links, hasSize(1));
|
||||
|
||||
MdmLink link = links.get(0);
|
||||
assertEquals(link.getSourcePid(), myFromGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getGoldenResourcePid(), myToGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getSourcePid(), myToGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getGoldenResourcePid(), myFromGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
|
||||
assertEquals(link.getMatchResult(), MdmMatchResultEnum.REDIRECT);
|
||||
assertEquals(link.getLinkSource(), MdmLinkSourceEnum.MANUAL);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
package ca.uhn.fhir.jpa.mdm.svc;
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
|
||||
import ca.uhn.fhir.jpa.mdm.helper.MdmLinkHelper;
|
||||
import ca.uhn.fhir.jpa.mdm.helper.testmodels.MDMState;
|
||||
import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
|
||||
import ca.uhn.fhir.mdm.api.IMdmLink;
|
||||
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
||||
import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
|
||||
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
|
||||
import ca.uhn.fhir.jpa.mdm.helper.MdmLinkHelper;
|
||||
import ca.uhn.fhir.mdm.interceptor.IMdmStorageInterceptor;
|
||||
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||
import ca.uhn.fhir.rest.server.TransactionLogMessages;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -21,12 +20,13 @@ import org.hl7.fhir.r4.model.Address;
|
|||
import org.hl7.fhir.r4.model.DateType;
|
||||
import org.hl7.fhir.r4.model.Enumerations;
|
||||
import org.hl7.fhir.r4.model.HumanName;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Identifier;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Example;
|
||||
|
||||
|
@ -39,9 +39,12 @@ import java.util.stream.Collectors;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(MdmGoldenResourceMergerSvcTest.class);
|
||||
|
||||
public static final String GIVEN_NAME = "Jenn";
|
||||
public static final String FAMILY_NAME = "Chan";
|
||||
|
@ -60,24 +63,16 @@ public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
|
|||
|
||||
private Patient myFromGoldenPatient;
|
||||
private Patient myToGoldenPatient;
|
||||
private Long myFromGoldenPatientPid;
|
||||
private Long myToGoldenPatientPid;
|
||||
private Patient myTargetPatient1;
|
||||
private Patient myTargetPatient2;
|
||||
private Patient myTargetPatient3;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
myFromGoldenPatient = createGoldenPatient();
|
||||
IdType fromSourcePatientId = myFromGoldenPatient.getIdElement().toUnqualifiedVersionless();
|
||||
myFromGoldenPatientPid = runInTransaction(()->myIdHelperService.getPidOrThrowException(RequestPartitionId.allPartitions(), fromSourcePatientId)).getIdAsLong();
|
||||
myToGoldenPatient = createGoldenPatient();
|
||||
IdType toGoldenPatientId = myToGoldenPatient.getIdElement().toUnqualifiedVersionless();
|
||||
myToGoldenPatientPid = runInTransaction(()->myIdHelperService.getPidOrThrowException(RequestPartitionId.allPartitions(), toGoldenPatientId)).getIdAsLong();
|
||||
|
||||
myTargetPatient1 = createPatient();
|
||||
myTargetPatient2 = createPatient();
|
||||
myTargetPatient3 = createPatient();
|
||||
|
||||
// Register the mdm storage interceptor after the creates so the delete hook is fired when we merge
|
||||
myInterceptorService.registerInterceptor(myMdmStorageInterceptor);
|
||||
|
@ -104,8 +99,19 @@ public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
|
|||
}
|
||||
|
||||
private Patient mergeGoldenPatients() {
|
||||
return mergeGoldenPatientsFlip(false);
|
||||
}
|
||||
|
||||
private Patient mergeGoldenPatientsFlip(boolean theFlipToAndFromGoldenResources) {
|
||||
assertEquals(0, redirectLinkCount());
|
||||
Patient retval = (Patient) myGoldenResourceMergerSvc.mergeGoldenResources(myFromGoldenPatient, null, myToGoldenPatient, createMdmContext());
|
||||
Patient from = theFlipToAndFromGoldenResources ? myToGoldenPatient : myFromGoldenPatient;
|
||||
Patient to = theFlipToAndFromGoldenResources ? myFromGoldenPatient : myToGoldenPatient;
|
||||
Patient retval = (Patient) myGoldenResourceMergerSvc.mergeGoldenResources(
|
||||
from,
|
||||
null,
|
||||
to,
|
||||
createMdmContext()
|
||||
);
|
||||
assertEquals(1, redirectLinkCount());
|
||||
return retval;
|
||||
}
|
||||
|
@ -123,31 +129,53 @@ public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void mergeRemovesPossibleDuplicatesLink() {
|
||||
MdmLink mdmLink = (MdmLink) myMdmLinkDaoSvc.newMdmLink()
|
||||
.setGoldenResourcePersistenceId(new ResourcePersistentId(myToGoldenPatientPid))
|
||||
.setSourcePersistenceId(new ResourcePersistentId(myFromGoldenPatientPid))
|
||||
.setMdmSourceType("Patient")
|
||||
.setMatchResult(MdmMatchResultEnum.POSSIBLE_DUPLICATE)
|
||||
.setLinkSource(MdmLinkSourceEnum.AUTO);
|
||||
public void PG1DuplicatesPG2_mergePG2toPG1_PG2RedirectsToPG1() {
|
||||
// setup
|
||||
String inputState = """
|
||||
GP1, AUTO, POSSIBLE_DUPLICATE, GP2
|
||||
""";
|
||||
MDMState<Patient> state = new MDMState<>();
|
||||
state.setInputState(inputState);
|
||||
myMdmLinkHelper.setup(state);
|
||||
|
||||
saveLink(mdmLink);
|
||||
// test
|
||||
mergeGoldenResources(
|
||||
state.getParameter("GP2"),
|
||||
state.getParameter("GP1")
|
||||
);
|
||||
|
||||
{
|
||||
List<MdmLink> foundLinks = myMdmLinkDao.findAll();
|
||||
assertEquals(1, foundLinks.size());
|
||||
assertEquals(MdmMatchResultEnum.POSSIBLE_DUPLICATE, foundLinks.get(0).getMatchResult());
|
||||
}
|
||||
// verify
|
||||
String outputState =
|
||||
"""
|
||||
GP2, MANUAL, REDIRECT, GP1
|
||||
""";
|
||||
state.setOutputState(outputState);
|
||||
myMdmLinkHelper.validateResults(state);
|
||||
}
|
||||
|
||||
myMdmLinkHelper.logMdmLinks();
|
||||
@Test
|
||||
public void GP1DuplicatesGP2_mergeGP1toGP2_GP1RedirectsToGP2() {
|
||||
// setup
|
||||
String inputState = """
|
||||
GP1, AUTO, POSSIBLE_DUPLICATE, GP2
|
||||
""";
|
||||
MDMState<Patient> state = new MDMState<>();
|
||||
state.setInputState(inputState);
|
||||
myMdmLinkHelper.setup(state);
|
||||
|
||||
mergeGoldenPatients();
|
||||
// test
|
||||
mergeGoldenResources(
|
||||
state.getParameter("GP1"),
|
||||
state.getParameter("GP2")
|
||||
);
|
||||
|
||||
{
|
||||
List<MdmLink> foundLinks = myMdmLinkDao.findAll();
|
||||
assertEquals(1, foundLinks.size());
|
||||
assertEquals(MdmMatchResultEnum.REDIRECT, foundLinks.get(0).getMatchResult());
|
||||
}
|
||||
// verify
|
||||
String outputState =
|
||||
"""
|
||||
GP1, MANUAL, REDIRECT, GP2
|
||||
""";
|
||||
state.setOutputState(outputState);
|
||||
myMdmLinkHelper.validateResults(state);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -213,19 +241,42 @@ public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
|
|||
assertThat(mergedSourcePatient, is(possibleLinkedTo(myTargetPatient1)));
|
||||
}
|
||||
|
||||
private Patient mergeGoldenResources(Patient theFrom, Patient theTo) {
|
||||
Patient retval = (Patient) myGoldenResourceMergerSvc.mergeGoldenResources(
|
||||
theFrom,
|
||||
null,
|
||||
theTo,
|
||||
createMdmContext()
|
||||
);
|
||||
assertEquals(1, redirectLinkCount());
|
||||
return retval;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromManualLinkOverridesAutoToLink() {
|
||||
MdmLink fromLink = createMdmLink(myFromGoldenPatient, myTargetPatient1);
|
||||
fromLink.setLinkSource(MdmLinkSourceEnum.MANUAL);
|
||||
fromLink.setMatchResult(MdmMatchResultEnum.MATCH);
|
||||
saveLink(fromLink);
|
||||
// setup
|
||||
String inputState = """
|
||||
GP2, MANUAL, MATCH, P1
|
||||
GP1, AUTO, POSSIBLE_MATCH, P1
|
||||
""";
|
||||
MDMState<Patient> state = new MDMState<>();
|
||||
state.setInputState(inputState);
|
||||
|
||||
createMdmLink(myToGoldenPatient, myTargetPatient1);
|
||||
myMdmLinkHelper.setup(state);
|
||||
|
||||
mergeGoldenPatients();
|
||||
List<MdmLink> links = getNonRedirectLinksByGoldenResource(myToGoldenPatient);
|
||||
assertEquals(1, links.size());
|
||||
assertEquals(MdmLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
|
||||
// test
|
||||
mergeGoldenResources(
|
||||
state.getParameter("GP2"), // from
|
||||
state.getParameter("GP1") // to
|
||||
);
|
||||
|
||||
// verify
|
||||
String outputState = """
|
||||
GP1, MANUAL, MATCH, P1
|
||||
GP2, MANUAL, REDIRECT, GP1
|
||||
""";
|
||||
state.setOutputState(outputState);
|
||||
myMdmLinkHelper.validateResults(state);
|
||||
}
|
||||
|
||||
private List<MdmLink> getNonRedirectLinksByGoldenResource(Patient theGoldenPatient) {
|
||||
|
@ -322,27 +373,44 @@ public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
|
|||
|
||||
mergeGoldenPatients();
|
||||
|
||||
assertResourceHasLinkCount(myToGoldenPatient, 3);
|
||||
assertResourceHasLinkCount(myFromGoldenPatient, 0);
|
||||
assertResourceHasLinkCount(myToGoldenPatient, 2);
|
||||
assertResourceHasLinkCount(myFromGoldenPatient, 1);
|
||||
// TODO ENSURE PROPER LINK TYPES
|
||||
assertEquals(3, myMdmLinkDao.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void from123To1() {
|
||||
createMdmLink(myFromGoldenPatient, myTargetPatient1);
|
||||
createMdmLink(myFromGoldenPatient, myTargetPatient2);
|
||||
createMdmLink(myFromGoldenPatient, myTargetPatient3);
|
||||
createMdmLink(myToGoldenPatient, myTargetPatient1);
|
||||
// init
|
||||
String inputState = """
|
||||
GP1, AUTO, POSSIBLE_MATCH, P1
|
||||
GP1, AUTO, POSSIBLE_MATCH, P2
|
||||
GP1, AUTO, POSSIBLE_MATCH, P3
|
||||
GP2, AUTO, POSSIBLE_MATCH, P1
|
||||
""";
|
||||
MDMState<Patient> state = new MDMState<>();
|
||||
state.setInputState(inputState);
|
||||
|
||||
mergeGoldenPatients();
|
||||
myMdmLinkHelper.setup(state);
|
||||
|
||||
// test
|
||||
mergeGoldenResources(
|
||||
state.getParameter("GP1"), // from
|
||||
state.getParameter("GP2") // to
|
||||
);
|
||||
myMdmLinkHelper.logMdmLinks();
|
||||
|
||||
assertThat(myToGoldenPatient, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3)));
|
||||
assertResourceHasAutoLinkCount(myToGoldenPatient, 3);
|
||||
// validate
|
||||
String outputState = """
|
||||
GP1, MANUAL, REDIRECT, GP2
|
||||
GP2, AUTO, POSSIBLE_MATCH, P1
|
||||
GP2, AUTO, POSSIBLE_MATCH, P2
|
||||
GP2, AUTO, POSSIBLE_MATCH, P3
|
||||
""";
|
||||
state.setOutputState(outputState);
|
||||
myMdmLinkHelper.validateResults(state);
|
||||
}
|
||||
|
||||
|
||||
private void assertResourceHasLinkCount(IBaseResource theResource, int theCount) {
|
||||
List<? extends IMdmLink> links = myMdmLinkDaoSvc.findMdmLinksByGoldenResource(theResource);
|
||||
assertEquals(theCount, links.size());
|
||||
|
@ -350,52 +418,95 @@ public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
|
|||
|
||||
@Test
|
||||
public void from1To123() {
|
||||
createMdmLink(myFromGoldenPatient, myTargetPatient1);
|
||||
createMdmLink(myToGoldenPatient, myTargetPatient1);
|
||||
createMdmLink(myToGoldenPatient, myTargetPatient2);
|
||||
createMdmLink(myToGoldenPatient, myTargetPatient3);
|
||||
// setup
|
||||
String inputState = """
|
||||
GP1, AUTO, POSSIBLE_MATCH, P1
|
||||
GP2, AUTO, POSSIBLE_MATCH, P1
|
||||
GP2, AUTO, POSSIBLE_MATCH, P2
|
||||
GP2, AUTO, POSSIBLE_MATCH, P3
|
||||
""";
|
||||
MDMState<Patient> state = new MDMState<>();
|
||||
state.setInputState(inputState);
|
||||
myMdmLinkHelper.setup(state);
|
||||
|
||||
// test
|
||||
mergeGoldenResources(
|
||||
state.getParameter("GP1"),
|
||||
state.getParameter("GP2")
|
||||
);
|
||||
|
||||
mergeGoldenPatients();
|
||||
myMdmLinkHelper.logMdmLinks();
|
||||
|
||||
assertThat(myToGoldenPatient, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3)));
|
||||
assertResourceHasAutoLinkCount(myToGoldenPatient, 3);
|
||||
}
|
||||
|
||||
private void assertResourceHasAutoLinkCount(Patient myToGoldenPatient, int theCount) {
|
||||
List<? extends IMdmLink> links = myMdmLinkDaoSvc.findMdmLinksByGoldenResource(myToGoldenPatient);
|
||||
assertEquals(theCount, links.stream().filter(IMdmLink::isAuto).count());
|
||||
// validate
|
||||
String outputState = """
|
||||
GP1, MANUAL, REDIRECT, GP2
|
||||
GP2, AUTO, POSSIBLE_MATCH, P1
|
||||
GP2, AUTO, POSSIBLE_MATCH, P2
|
||||
GP2, AUTO, POSSIBLE_MATCH, P3
|
||||
""";
|
||||
state.setOutputState(outputState);
|
||||
myMdmLinkHelper.validateResults(state);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void from123To123() {
|
||||
createMdmLink(myFromGoldenPatient, myTargetPatient1);
|
||||
createMdmLink(myFromGoldenPatient, myTargetPatient2);
|
||||
createMdmLink(myFromGoldenPatient, myTargetPatient3);
|
||||
createMdmLink(myToGoldenPatient, myTargetPatient1);
|
||||
createMdmLink(myToGoldenPatient, myTargetPatient2);
|
||||
createMdmLink(myToGoldenPatient, myTargetPatient3);
|
||||
// setup
|
||||
String inputState = """
|
||||
GP1, AUTO, POSSIBLE_MATCH, P1
|
||||
GP1, AUTO, POSSIBLE_MATCH, P2
|
||||
GP1, AUTO, POSSIBLE_MATCH, P3
|
||||
GP2, AUTO, POSSIBLE_MATCH, P1
|
||||
GP2, AUTO, POSSIBLE_MATCH, P2
|
||||
GP2, AUTO, POSSIBLE_MATCH, P3
|
||||
""";
|
||||
MDMState<Patient> state = new MDMState<>();
|
||||
state.setInputState(inputState);
|
||||
myMdmLinkHelper.setup(state);
|
||||
|
||||
mergeGoldenPatients();
|
||||
// test
|
||||
mergeGoldenResources(state.getParameter("GP1"), state.getParameter("GP2"));
|
||||
|
||||
assertThat(myToGoldenPatient, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3)));
|
||||
|
||||
assertResourceHasAutoLinkCount(myToGoldenPatient, 3);
|
||||
// verify
|
||||
String outputState = """
|
||||
GP1, MANUAL, REDIRECT, GP2
|
||||
GP2, AUTO, POSSIBLE_MATCH, P1
|
||||
GP2, AUTO, POSSIBLE_MATCH, P2
|
||||
GP2, AUTO, POSSIBLE_MATCH, P3
|
||||
""";
|
||||
state.setOutputState(outputState);
|
||||
myMdmLinkHelper.validateResults(state);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void from12To23() {
|
||||
createMdmLink(myFromGoldenPatient, myTargetPatient1);
|
||||
createMdmLink(myFromGoldenPatient, myTargetPatient2);
|
||||
createMdmLink(myToGoldenPatient, myTargetPatient2);
|
||||
createMdmLink(myToGoldenPatient, myTargetPatient3);
|
||||
// setup
|
||||
String inputState = """
|
||||
GP1, AUTO, POSSIBLE_MATCH, P1
|
||||
GP1, AUTO, POSSIBLE_MATCH, P2
|
||||
GP2, AUTO, POSSIBLE_MATCH, P2
|
||||
GP2, AUTO, POSSIBLE_MATCH, P3
|
||||
""";
|
||||
MDMState<Patient> state = new MDMState<>();
|
||||
state.setInputState(inputState);
|
||||
|
||||
mergeGoldenPatients();
|
||||
myMdmLinkHelper.setup(state);
|
||||
|
||||
// test
|
||||
mergeGoldenResources(
|
||||
state.getParameter("GP1"), // from
|
||||
state.getParameter("GP2") // to
|
||||
);
|
||||
myMdmLinkHelper.logMdmLinks();
|
||||
|
||||
assertThat(myToGoldenPatient, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3)));
|
||||
|
||||
assertResourceHasAutoLinkCount(myToGoldenPatient, 3);
|
||||
// validate
|
||||
String outputState = """
|
||||
GP1, MANUAL, REDIRECT, GP2
|
||||
GP2, AUTO, POSSIBLE_MATCH, P1
|
||||
GP2, AUTO, POSSIBLE_MATCH, P2
|
||||
GP2, AUTO, POSSIBLE_MATCH, P3
|
||||
""";
|
||||
state.setOutputState(outputState);
|
||||
myMdmLinkHelper.validateResults(state);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -476,4 +587,5 @@ public class MdmGoldenResourceMergerSvcTest extends BaseMdmR4Test {
|
|||
address.setPostalCode(POSTAL_CODE);
|
||||
theSourcePatient.setAddress(Collections.singletonList(address));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
|||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
import java.util.Date;
|
||||
|
|
|
@ -93,7 +93,7 @@ public final class MdmResourceUtil {
|
|||
|
||||
/**
|
||||
* Sets the MDM-managed tag, indicating the MDM system has ownership of this
|
||||
* Resource. No changes are made if resource is already maanged by MDM.
|
||||
* Resource. No changes are made if resource is already managed by MDM.
|
||||
*
|
||||
* @param theBaseResource resource to set the tag for
|
||||
* @return Returns resource with the tag set.
|
||||
|
|
|
@ -9,28 +9,23 @@ import ca.uhn.fhir.rest.api.Constants;
|
|||
import ca.uhn.fhir.sl.cache.Cache;
|
||||
import ca.uhn.fhir.sl.cache.CacheFactory;
|
||||
import ca.uhn.fhir.system.HapiSystemProperties;
|
||||
import ca.uhn.fhir.util.CoverageIgnore;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.fhir.ucum.UcumService;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.TerminologyServiceException;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.context.IWorkerContextManager;
|
||||
import org.hl7.fhir.r5.formats.IParser;
|
||||
import org.hl7.fhir.r5.formats.ParserType;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r5.model.CodeableConcept;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.ConceptMap;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
|
||||
import org.hl7.fhir.r5.model.NamingSystem;
|
||||
import org.hl7.fhir.r5.model.Parameters;
|
||||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.model.ResourceType;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.model.StructureMap;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
|
||||
import org.hl7.fhir.r5.terminologies.ValueSetExpander;
|
||||
|
|
Loading…
Reference in New Issue