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:
TipzCM 2022-12-05 09:04:51 -05:00 committed by GitHub
parent 312128754b
commit 08ca687c72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 798 additions and 136 deletions

View File

@ -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).
"

View File

@ -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).
"

View File

@ -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);
}

View File

@ -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));
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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));
}
}

View File

@ -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;

View File

@ -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.

View File

@ -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;