Pre-review commit

This commit is contained in:
Nick 2021-01-14 20:35:09 -05:00
parent 759f06ce48
commit 6cc40b134e
9 changed files with 78 additions and 65 deletions

View File

@ -251,15 +251,6 @@ Use the `$mdm-update-link` operation to change the `matchResult` update of an md
Must be either MATCH or NO_MATCH.
</td>
</tr>
<tr>
<td>resource</td>
<td>Resource</td>
<td>0.1</td>
<td>
Optional manually merged Golden Resource. All values except for the metadata, PID and identifiers will be copied from this resource, if it is present. If no value is specified, all fields from the resource pointed to by "resourceId" will be copied instead.
.
</td>
</tr>
</tbody>
</table>

View File

@ -20,17 +20,18 @@ package ca.uhn.fhir.jpa.mdm.svc;
* #L%
*/
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.mdm.api.IGoldenResourceMergerSvc;
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
import ca.uhn.fhir.mdm.util.TerserUtil;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.slf4j.Logger;
@ -65,16 +66,24 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
Long toGoldenResourcePid = myIdHelperService.getPidOrThrowException(theToGoldenResource);
String resourceType = theMdmTransactionContext.getResourceType();
// Merge attributes, to be determined when survivorship is solved.
myGoldenResourceHelper.mergeIndentifierFields(theFromGoldenResource, theToGoldenResource, theMdmTransactionContext);
if (theMergedResource != null ) {
if (myGoldenResourceHelper.hasIdentifier(theMergedResource)) {
throw new IllegalArgumentException("Manually merged resource can not contain identifiers");
}
myGoldenResourceHelper.mergeIndentifierFields(theFromGoldenResource, theMergedResource, theMdmTransactionContext);
myGoldenResourceHelper.mergeIndentifierFields(theToGoldenResource, theMergedResource, theMdmTransactionContext);
IAnyResource mergeSource = ( theMergedResource == null ) ? theFromGoldenResource : theMergedResource;
myGoldenResourceHelper.mergeNonIdentiferFields(mergeSource, theToGoldenResource, theMdmTransactionContext);
theMergedResource.setId(theToGoldenResource.getId());
theToGoldenResource = (IAnyResource) myMdmResourceDaoSvc.upsertGoldenResource(theMergedResource, resourceType).getResource();
} else {
myGoldenResourceHelper.mergeIndentifierFields(theFromGoldenResource, theToGoldenResource, theMdmTransactionContext);
myGoldenResourceHelper.mergeNonIdentiferFields(theFromGoldenResource, theToGoldenResource, theMdmTransactionContext);
//Save changes to the golden resource
myMdmResourceDaoSvc.upsertGoldenResource(theToGoldenResource, resourceType);
}
//Merge the links from the FROM to the TO resource. Clean up dangling links.
mergeGoldenResourceLinks(theFromGoldenResource, theToGoldenResource, toGoldenResourcePid, theMdmTransactionContext);
//Save changes to the golden resource
myMdmResourceDaoSvc.upsertGoldenResource(theToGoldenResource, resourceType);
//Create the new REDIRECT link
addMergeLink(toGoldenResourcePid, fromGoldenResourcePid, resourceType);

View File

@ -94,8 +94,11 @@ public class MdmLinkUpdaterSvcImpl implements IMdmLinkUpdaterSvc {
mdmLink.setLinkSource(MdmLinkSourceEnum.MANUAL);
myMdmLinkDaoSvc.save(mdmLink);
IAnyResource resource = (theManuallyMergedGoldenResource == null) ? theSourceResource : theManuallyMergedGoldenResource;
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(resource, theGoldenResource, theMdmContext);
// IAnyResource resource = (theManuallyMergedGoldenResource == null) ? theSourceResource : theManuallyMergedGoldenResource;
if (theMatchResult == MdmMatchResultEnum.MATCH) {
// only apply survivorship rules in case of a match
myMdmSurvivorshipService.applySurvivorshipRulesToGoldenResource(theSourceResource, theGoldenResource, theMdmContext);
}
myMdmResourceDaoSvc.upsertGoldenResource(theGoldenResource, theMdmContext.getResourceType());
if (theMatchResult == MdmMatchResultEnum.NO_MATCH) {

View File

@ -34,6 +34,8 @@ public class MdmSurvivorshipSvcImpl implements IMdmSurvivorshipService {
private FhirContext myFhirContext;
/**
* TODO NG Update docs to tell taht we also do that in non GR merge - like linking
*
* Merges two golden resources by overwriting all field values on theGoldenResource param for all REST operation methods
* except MERGE_GOLDEN_RESOURCES. In case of MERGE_GOLDEN_RESOURCES, it will attempt to copy field values from
* theTargetResource that do not exist in theGoldenResource. PID, indentifiers and meta values are not affected by

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
import ca.uhn.fhir.mdm.util.TerserUtil;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.r4.model.Patient;
@ -95,12 +96,11 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
@Test
public void testMergeWithManualOverride() {
Patient mergedSourcePatient = (Patient) myMdmProvider.mergeGoldenResources(myFromGoldenPatientId,
myToGoldenPatientId, myFromGoldenPatient, myRequestDetails);
Patient patient = TerserUtil.clone(myFhirContext, myFromGoldenPatient);
patient.setIdElement(null);
myFromGoldenPatient = (Patient) myPatientDao.read(myFromGoldenPatient.getIdElement().toUnqualifiedVersionless());
assertTrue(!MdmResourceUtil.isGoldenRecord(myFromGoldenPatient));
assertTrue(MdmResourceUtil.isGoldenRecordRedirected(myFromGoldenPatient));
Patient mergedSourcePatient = (Patient) myMdmProvider.mergeGoldenResources(myFromGoldenPatientId,
myToGoldenPatientId, patient, myRequestDetails);
assertEquals(myToGoldenPatient.getIdElement(), mergedSourcePatient.getIdElement());
assertThat(mergedSourcePatient, is(sameGoldenResourceAs(myToGoldenPatient)));

View File

@ -12,7 +12,6 @@ import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Date;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
@ -32,7 +31,7 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
@Test
public void testUpdateLinkNoMatch() {
assertLinkCount(1);
myMdmProvider.updateLink(mySourcePatientId, myPatientId, NO_MATCH_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(mySourcePatientId, myPatientId, NO_MATCH_RESULT, myRequestDetails);
assertLinkCount(2);
List<MdmLink> links = getPatientLinks();
@ -46,7 +45,7 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
@Test
public void testUpdateLinkMatch() {
assertLinkCount(1);
myMdmProvider.updateLink(mySourcePatientId, myPatientId, MATCH_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(mySourcePatientId, myPatientId, MATCH_RESULT, myRequestDetails);
assertLinkCount(1);
List<MdmLink> links = getPatientLinks();
@ -54,34 +53,14 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
assertEquals(MdmMatchResultEnum.MATCH, links.get(0).getMatchResult());
}
@Test
public void testUpdateLinkWithOverride() {
assertLinkCount(1);
Patient patient = new Patient();
patient.addName().addGiven("Given");
patient.addName().setFamily("Family");
patient.setBirthDate(new Date());
Patient updatedPerson = (Patient) myMdmProvider
.updateLink(mySourcePatientId, myPatientId, MATCH_RESULT, patient, myRequestDetails);
assertLinkCount(1);
List<MdmLink> links = getPatientLinks();
assertEquals(MdmLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
assertEquals(MdmMatchResultEnum.MATCH, links.get(0).getMatchResult());
assertEquals(patient.getNameFirstRep().getNameAsSingleString(), updatedPerson.getNameFirstRep().getNameAsSingleString());
assertEquals(patient.getBirthDate(), updatedPerson.getBirthDate());
}
@Test
public void testUpdateLinkTwiceFailsDueToWrongVersion() {
myMdmProvider.updateLink(mySourcePatientId, myPatientId, MATCH_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(mySourcePatientId, myPatientId, MATCH_RESULT, myRequestDetails);
materiallyChangeGoldenPatient();
try {
myMdmProvider.updateLink(mySourcePatientId, myPatientId, NO_MATCH_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(mySourcePatientId, myPatientId, NO_MATCH_RESULT, myRequestDetails);
fail();
} catch (ResourceVersionConflictException e) {
assertThat(e.getMessage(), matchesPattern("Requested resource Patient/\\d+/_history/1 is not the latest version. Latest version is Patient/\\d+/_history/2"));
@ -96,19 +75,19 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
@Test
public void testUpdateLinkTwiceDoesNotThrowValidationErrorWhenNoVersionIsProvided() {
myMdmProvider.updateLink(mySourcePatientId, myPatientId, MATCH_RESULT, null, myRequestDetails);
Patient patient = (Patient) myMdmProvider.updateLink(myVersionlessGodlenResourceId, myPatientId, NO_MATCH_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(mySourcePatientId, myPatientId, MATCH_RESULT, myRequestDetails);
Patient patient = (Patient) myMdmProvider.updateLink(myVersionlessGodlenResourceId, myPatientId, NO_MATCH_RESULT, myRequestDetails);
assertNotNull(patient); // if this wasn't allowed - a validation exception would be thrown
}
@Test
public void testUnlinkLink() {
myMdmProvider.updateLink(mySourcePatientId, myPatientId, NO_MATCH_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(mySourcePatientId, myPatientId, NO_MATCH_RESULT, myRequestDetails);
materiallyChangeGoldenPatient();
try {
myMdmProvider.updateLink(mySourcePatientId, myPatientId, MATCH_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(mySourcePatientId, myPatientId, MATCH_RESULT, myRequestDetails);
fail();
} catch (ResourceVersionConflictException e) {
assertThat(e.getMessage(), matchesPattern("Requested resource Patient/\\d+/_history/1 is not the latest version. Latest version is Patient/\\d+/_history/2"));
@ -118,7 +97,7 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
@Test
public void testUpdateIllegalResultForPossibleMatch() {
try {
myMdmProvider.updateLink(mySourcePatientId, myPatientId, POSSIBLE_MATCH_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(mySourcePatientId, myPatientId, POSSIBLE_MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("$mdm-update-link illegal matchResult value 'POSSIBLE_MATCH'. Must be NO_MATCH or MATCH", e.getMessage());
@ -128,7 +107,7 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
@Test
public void testUpdateIllegalResultPD() {
try {
myMdmProvider.updateLink(mySourcePatientId, myPatientId, POSSIBLE_DUPLICATE_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(mySourcePatientId, myPatientId, POSSIBLE_DUPLICATE_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("$mdm-update-link illegal matchResult value 'POSSIBLE_DUPLICATE'. Must be NO_MATCH or MATCH", e.getMessage());
@ -138,7 +117,7 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
@Test
public void testUpdateIllegalSecondArg() {
try {
myMdmProvider.updateLink(myPatientId, new StringType(""), NO_MATCH_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(myPatientId, new StringType(""), NO_MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), endsWith(" must have form <resourceType>/<id> where <id> is the id of the resource and <resourceType> is the type of the resource"));
@ -148,7 +127,7 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
@Test
public void testUpdateIllegalFirstArg() {
try {
myMdmProvider.updateLink(new StringType(""), myPatientId, NO_MATCH_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(new StringType(""), myPatientId, NO_MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), endsWith(" must have form <resourceType>/<id> where <id> is the id of the resource"));
@ -158,7 +137,7 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
@Test
public void testAttemptingToModifyANonExistentLinkFails() {
try {
myMdmProvider.updateLink(mySourcePatientId, mySourcePatientId, NO_MATCH_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(mySourcePatientId, mySourcePatientId, NO_MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), startsWith("No link"));
@ -169,7 +148,7 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
public void testUpdateStrangePatient() {
Patient patient = createPatient();
try {
myMdmProvider.updateLink(new StringType(patient.getIdElement().getValue()), myPatientId, NO_MATCH_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(new StringType(patient.getIdElement().getValue()), myPatientId, NO_MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
String expectedMessage = myMessageHelper.getMessageForUnmanagedResource();
@ -183,7 +162,7 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
patient.getMeta().addTag().setSystem(MdmConstants.SYSTEM_MDM_MANAGED).setCode(MdmConstants.CODE_NO_MDM_MANAGED);
createPatient(patient);
try {
myMdmProvider.updateLink(mySourcePatientId, new StringType(patient.getIdElement().getValue()), NO_MATCH_RESULT, null, myRequestDetails);
myMdmProvider.updateLink(mySourcePatientId, new StringType(patient.getIdElement().getValue()), NO_MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals(myMessageHelper.getMessageForUnsupportedSourceResource(), e.getMessage());

View File

@ -159,10 +159,9 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider {
public IBaseResource updateLink(@OperationParam(name = ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, min = 1, max = 1) IPrimitiveType<String> theGoldenResourceId,
@OperationParam(name = ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, min = 1, max = 1) IPrimitiveType<String> theResourceId,
@OperationParam(name = ProviderConstants.MDM_UPDATE_LINK_MATCH_RESULT, min = 1, max = 1) IPrimitiveType<String> theMatchResult,
@OperationParam(name = ProviderConstants.MDM_UPDATE_LINK_RESOURCE, max = 1) IAnyResource theManuallyMergedResource,
ServletRequestDetails theRequestDetails) {
validateUpdateLinkParameters(theGoldenResourceId, theResourceId, theMatchResult);
return myMdmControllerSvc.updateLink(theGoldenResourceId.getValueAsString(), theResourceId.getValue(), theManuallyMergedResource,
return myMdmControllerSvc.updateLink(theGoldenResourceId.getValueAsString(), theResourceId.getValue(), null,
theMatchResult.getValue(), createMdmContext(theRequestDetails, MdmTransactionContext.OperationType.UPDATE_LINK,
getResourceType(ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId))
);

View File

@ -241,6 +241,10 @@ public class GoldenResourceHelper {
}
}
public boolean hasIdentifier(IBaseResource theResource) {
return TerserUtil.hasValues(myFhirContext, theResource, FIELD_NAME_IDENTIFIER);
}
public void mergeIndentifierFields(IBaseResource theFromGoldenResource, IBaseResource theToGoldenResource, MdmTransactionContext theMdmTransactionContext) {
TerserUtil.cloneCompositeField(myFhirContext, theFromGoldenResource, theToGoldenResource, FIELD_NAME_IDENTIFIER);
}

View File

@ -89,6 +89,23 @@ public final class TerserUtil {
theIdentifierDefinition.getMutator().addValue(theResourceToCloneEidInto, resourceNewIdentifier);
}
/**
* Checks if the specified fields has any values
*
* @param theFhirContext Context holding resource definition
* @param theResource Resource to check if the specified field is set
* @param theFieldName name of the field to check
* @return Returns true if field exists and has any values set, and false otherwise
*/
public static boolean hasValues(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) {
RuntimeResourceDefinition resourceDefinition = theFhirContext.getResourceDefinition(theResource);
BaseRuntimeChildDefinition resourceIdentifier = resourceDefinition.getChildByName(theFieldName);
if (resourceIdentifier == null) {
return false;
}
return !(resourceIdentifier.getAccessor().getValues(theResource).isEmpty());
}
/**
* Clones specified composite field (collection). Composite field values must confirm to the collections
* contract.
@ -237,4 +254,13 @@ public final class TerserUtil {
}
}
public static <T extends IBaseResource> T clone(FhirContext theFhirContext, T theInstance) {
RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theInstance.getClass());
T retVal = (T) definition.newInstance();
FhirTerser terser = theFhirContext.newTerser();
terser.cloneInto(theInstance, retVal, true);
return retVal;
}
}