From 3c80dba90e25c3e4f54324b860b457afb2f86f35 Mon Sep 17 00:00:00 2001 From: katiesmilecdr <88786813+katiesmilecdr@users.noreply.github.com> Date: Thu, 7 Oct 2021 13:42:21 -0400 Subject: [PATCH] [3040] finish implementation (#3049) * [3040] finish implementation * Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_mdm/mdm_operations.md Co-authored-by: Tadgh * Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_mdm/mdm_operations.md Co-authored-by: Tadgh * Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_mdm/mdm_operations.md Co-authored-by: Tadgh * [3040] throw exception if link exists * [3040] add matchResult parameter/update documentation Co-authored-by: Tadgh --- .../3040-Manual-Matching-Requirement.yaml | 5 + .../docs/server_jpa_mdm/mdm_operations.md | 72 +++++++ .../jpa/mdm/config/MdmConsumerConfig.java | 8 + .../jpa/mdm/svc/MdmControllerSvcImpl.java | 14 ++ .../jpa/mdm/svc/MdmLinkCreateSvcImpl.java | 96 ++++++++++ .../provider/MdmProviderCreateLinkR4Test.java | 175 ++++++++++++++++++ .../uhn/fhir/mdm/api/IMdmControllerSvc.java | 2 + .../uhn/fhir/mdm/api/IMdmLinkCreateSvc.java | 10 + .../fhir/mdm/model/MdmTransactionContext.java | 1 + .../fhir/mdm/provider/BaseMdmProvider.java | 18 ++ .../mdm/provider/MdmProviderDstu3Plus.java | 13 ++ .../ca/uhn/fhir/mdm/util/MessageHelper.java | 17 ++ .../server/provider/ProviderConstants.java | 5 + 13 files changed, 436 insertions(+) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_6_0/3040-Manual-Matching-Requirement.yaml create mode 100644 hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkCreateSvcImpl.java create mode 100644 hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/provider/MdmProviderCreateLinkR4Test.java create mode 100644 hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmLinkCreateSvc.java diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_6_0/3040-Manual-Matching-Requirement.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_6_0/3040-Manual-Matching-Requirement.yaml new file mode 100644 index 00000000000..b1204ceeb6b --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_6_0/3040-Manual-Matching-Requirement.yaml @@ -0,0 +1,5 @@ +--- +type: add +issue: 3020 +jira: SMILE-3192 +title: "Added `$mdm-create-link` operation and documentation." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_mdm/mdm_operations.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_mdm/mdm_operations.md index 369ae007b9d..a608498456a 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_mdm/mdm_operations.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_mdm/mdm_operations.md @@ -396,6 +396,78 @@ Any supported MDM type can be used. The following request body shows how to upda The operation returns the updated Golden Resource. For the query above `Patient` resource will be returned. Note that this is the only way to modify MDM-managed Golden Resources. +## Create Link + +Use the `$mdm-create-link` operation to create an MDM link from a Golden Resource to a Target Resource without the need for any pre-existing matching data within the two resources. This operation takes the following parameters: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeCardinalityDescription
goldenResourceIdString1..1 + The id of the Golden Resource. +
resourceIdString1..1 + The id of the target resource. +
matchResultString0..1 + Optional matchResult. If omitted, it automatically set the default to MATCH, otherwise the value should be +MATCH, POSSIBLE_MATCH or NO_MATCH. +
+ +MDM links created in this way will automatically have their `linkSource` set to `MANUAL`. + +### Example + +Use an HTTP POST to the following URL to invoke this operation: + +```url +http://example.com/$mdm-create-link +``` + +Any supported MDM type can be used. The following request body shows how to update link on the Patient resource type: + +```json +{ + "resourceType": "Parameters", + "parameter": [ { + "name": "goldenResourceId", + "valueString": "Patient/123" + }, { + "name": "resourceId", + "valueString": "Patient/456" + }, { + "name": "matchResult", + "valueString": "MATCH" + } ] +} +``` + +The operation returns the Golden Resource. For the query above, `Patient` will be returned. + ## Merge Golden Resources The `$mdm-merge-golden-resources` operation can be used to merge one Golden Resource with another. When doing this, you will need to decide which resource to merge from and which one to merge to. diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/config/MdmConsumerConfig.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/config/MdmConsumerConfig.java index e1b65abffc0..48c29f97a0c 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/config/MdmConsumerConfig.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/config/MdmConsumerConfig.java @@ -35,6 +35,7 @@ import ca.uhn.fhir.jpa.mdm.svc.GoldenResourceMergerSvcImpl; import ca.uhn.fhir.jpa.mdm.svc.IMdmModelConverterSvc; import ca.uhn.fhir.jpa.mdm.svc.MdmControllerSvcImpl; import ca.uhn.fhir.jpa.mdm.svc.MdmEidUpdateService; +import ca.uhn.fhir.jpa.mdm.svc.MdmLinkCreateSvcImpl; import ca.uhn.fhir.jpa.mdm.svc.MdmLinkQuerySvcImplSvc; import ca.uhn.fhir.jpa.mdm.svc.MdmLinkSvcImpl; import ca.uhn.fhir.jpa.mdm.svc.MdmLinkUpdaterSvcImpl; @@ -55,6 +56,7 @@ import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmGoldenResourceFindingSvc; import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc; import ca.uhn.fhir.mdm.api.IMdmBatchJobSubmitterFactory; import ca.uhn.fhir.mdm.api.IMdmControllerSvc; +import ca.uhn.fhir.mdm.api.IMdmLinkCreateSvc; import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc; import ca.uhn.fhir.mdm.api.IMdmLinkSvc; import ca.uhn.fhir.mdm.api.IMdmLinkUpdaterSvc; @@ -233,6 +235,12 @@ public class MdmConsumerConfig { return new MdmLinkUpdaterSvcImpl(); } + @Bean + IMdmLinkCreateSvc mdmLinkCreateSvc() { + return new MdmLinkCreateSvcImpl(); + } + + @Bean MdmLoader mdmLoader() { return new MdmLoader(); diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmControllerSvcImpl.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmControllerSvcImpl.java index d6eaab73eaf..ec54d957e44 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmControllerSvcImpl.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmControllerSvcImpl.java @@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc; import ca.uhn.fhir.mdm.api.IMdmBatchJobSubmitterFactory; import ca.uhn.fhir.mdm.api.IMdmControllerSvc; +import ca.uhn.fhir.mdm.api.IMdmLinkCreateSvc; import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc; import ca.uhn.fhir.mdm.api.IMdmLinkUpdaterSvc; import ca.uhn.fhir.mdm.api.MdmLinkJson; @@ -65,6 +66,8 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc { @Autowired IMdmLinkUpdaterSvc myIMdmLinkUpdaterSvc; @Autowired + IMdmLinkCreateSvc myIMdmLinkCreateSvc; + @Autowired IMdmBatchJobSubmitterFactory myMdmBatchJobSubmitterFactory; public MdmControllerSvcImpl() { @@ -106,6 +109,17 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc { return myIMdmLinkUpdaterSvc.updateLink(goldenResource, source, matchResult, theMdmTransactionContext); } + @Override + public IAnyResource createLink(String theGoldenResourceId, String theSourceResourceId, @Nullable String theMatchResult, MdmTransactionContext theMdmTransactionContext) { + MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult); + IAnyResource goldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_CREATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId); + IAnyResource source = myMdmControllerHelper.getLatestSourceFromIdOrThrowException(ProviderConstants.MDM_CREATE_LINK_RESOURCE_ID, theSourceResourceId); + myMdmControllerHelper.validateSameVersion(goldenResource, theGoldenResourceId); + myMdmControllerHelper.validateSameVersion(source, theSourceResourceId); + + return myIMdmLinkCreateSvc.createLink(goldenResource, source, matchResult, theMdmTransactionContext); + } + @Override public IBaseParameters submitMdmClearJob(List theUrls, IPrimitiveType theBatchSize, ServletRequestDetails theRequestDetails) { MultiUrlProcessor multiUrlProcessor = new MultiUrlProcessor(myFhirContext, myMdmBatchJobSubmitterFactory.getClearJobSubmitter()); diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkCreateSvcImpl.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkCreateSvcImpl.java new file mode 100644 index 00000000000..5226e8efbc1 --- /dev/null +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/svc/MdmLinkCreateSvcImpl.java @@ -0,0 +1,96 @@ +package ca.uhn.fhir.jpa.mdm.svc; + +import ca.uhn.fhir.context.FhirContext; +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.IMdmLinkCreateSvc; +import ca.uhn.fhir.mdm.api.IMdmSettings; +import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; +import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; +import ca.uhn.fhir.mdm.log.Logs; +import ca.uhn.fhir.mdm.model.MdmTransactionContext; +import ca.uhn.fhir.mdm.util.MdmResourceUtil; +import ca.uhn.fhir.mdm.util.MessageHelper; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class MdmLinkCreateSvcImpl implements IMdmLinkCreateSvc { + private static final Logger ourLog = Logs.getMdmTroubleshootingLog(); + + @Autowired + FhirContext myFhirContext; + @Autowired + IdHelperService myIdHelperService; + @Autowired + MdmLinkDaoSvc myMdmLinkDaoSvc; + @Autowired + IMdmSettings myMdmSettings; + @Autowired + MessageHelper myMessageHelper; + + @Transactional + @Override + public IAnyResource createLink(IAnyResource theGoldenResource, IAnyResource theSourceResource, MdmMatchResultEnum theMatchResult, MdmTransactionContext theMdmContext) { + String sourceType = myFhirContext.getResourceType(theSourceResource); + + validateCreateLinkRequest(theGoldenResource, theSourceResource, sourceType); + + Long goldenResourceId = myIdHelperService.getPidOrThrowException(theGoldenResource); + Long targetId = myIdHelperService.getPidOrThrowException(theSourceResource); + + Optional optionalMdmLink = myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndSourceResourcePid(goldenResourceId, targetId); + if (optionalMdmLink.isPresent()) { + throw new InvalidRequestException(myMessageHelper.getMessageForPresentLink(theGoldenResource, theSourceResource)); + } + + List mdmLinks = myMdmLinkDaoSvc.getMdmLinksBySourcePidAndMatchResult(targetId, MdmMatchResultEnum.MATCH); + if (mdmLinks.size() > 0 && theMatchResult == MdmMatchResultEnum.MATCH) { + throw new InvalidRequestException(myMessageHelper.getMessageForMultipleGoldenRecords(theSourceResource)); + } + + MdmLink mdmLink = myMdmLinkDaoSvc.getOrCreateMdmLinkByGoldenResourcePidAndSourceResourcePid(goldenResourceId, targetId); + mdmLink.setLinkSource(MdmLinkSourceEnum.MANUAL); + if (theMatchResult == null) { + mdmLink.setMatchResult(MdmMatchResultEnum.MATCH); + } else { + mdmLink.setMatchResult(theMatchResult); + } + ourLog.info("Manually creating a " + theGoldenResource.getIdElement().toVersionless() + " to " + theSourceResource.getIdElement().toVersionless() + " mdm link."); + myMdmLinkDaoSvc.save(mdmLink); + + return theGoldenResource; + } + + private void validateCreateLinkRequest(IAnyResource theGoldenRecord, IAnyResource theSourceResource, String theSourceType) { + String goldenRecordType = myFhirContext.getResourceType(theGoldenRecord); + + if (!myMdmSettings.isSupportedMdmType(goldenRecordType)) { + throw new InvalidRequestException(myMessageHelper.getMessageForUnsupportedFirstArgumentTypeInUpdate(goldenRecordType)); + } + + if (!myMdmSettings.isSupportedMdmType(theSourceType)) { + throw new InvalidRequestException(myMessageHelper.getMessageForUnsupportedSecondArgumentTypeInUpdate(theSourceType)); + } + + if (!Objects.equals(goldenRecordType, theSourceType)) { + throw new InvalidRequestException(myMessageHelper.getMessageForArgumentTypeMismatchInUpdate(goldenRecordType, theSourceType)); + } + + if (!MdmResourceUtil.isMdmManaged(theGoldenRecord)) { + throw new InvalidRequestException(myMessageHelper.getMessageForUnmanagedResource()); + } + + if (!MdmResourceUtil.isMdmAllowed(theSourceResource)) { + throw new InvalidRequestException(myMessageHelper.getMessageForUnsupportedSourceResource()); + } + } +} diff --git a/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/provider/MdmProviderCreateLinkR4Test.java b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/provider/MdmProviderCreateLinkR4Test.java new file mode 100644 index 00000000000..4b19d53f2e9 --- /dev/null +++ b/hapi-fhir-jpaserver-mdm/src/test/java/ca/uhn/fhir/jpa/mdm/provider/MdmProviderCreateLinkR4Test.java @@ -0,0 +1,175 @@ +package ca.uhn.fhir.jpa.mdm.provider; +import ca.uhn.fhir.jpa.entity.MdmLink; +import ca.uhn.fhir.mdm.api.MdmConstants; +import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum; +import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; +import ca.uhn.fhir.mdm.util.MessageHelper; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.StringType; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class MdmProviderCreateLinkR4Test extends BaseLinkR4Test { + + @Autowired + private MessageHelper myMessageHelper; + + @Test + public void testCreateLinkWithMatchResult() { + assertLinkCount(1); + + Patient patient = createPatient(buildPatientWithNameAndId("PatientGiven", "ID.PatientGiven.123"), true, false); + StringType patientId = new StringType(patient.getIdElement().getValue()); + + Patient sourcePatient = createPatient(buildPatientWithNameAndId("SourcePatientGiven", "ID.SourcePatientGiven.123"), true, false); + StringType sourcePatientId = new StringType(sourcePatient.getIdElement().getValue()); + + myMdmProvider.createLink(sourcePatientId, patientId, MATCH_RESULT, myRequestDetails); + assertLinkCount(2); + + List links = myMdmLinkDaoSvc.findMdmLinksBySourceResource(patient); + assertEquals(MdmLinkSourceEnum.MANUAL, links.get(0).getLinkSource()); + assertEquals(MdmMatchResultEnum.MATCH, links.get(0).getMatchResult()); + } + + @Test + public void testCreateLinkWithNullMatchResult() { + assertLinkCount(1); + + Patient patient = createPatient(buildPatientWithNameAndId("PatientGiven", "ID.PatientGiven.123"), true, false); + StringType patientId = new StringType(patient.getIdElement().getValue()); + + Patient sourcePatient = createPatient(buildPatientWithNameAndId("SourcePatientGiven", "ID.SourcePatientGiven.123"), true, false); + StringType sourcePatientId = new StringType(sourcePatient.getIdElement().getValue()); + + myMdmProvider.createLink(sourcePatientId, patientId, null, myRequestDetails); + assertLinkCount(2); + + List links = myMdmLinkDaoSvc.findMdmLinksBySourceResource(patient); + assertEquals(MdmLinkSourceEnum.MANUAL, links.get(0).getLinkSource()); + assertEquals(MdmMatchResultEnum.MATCH, links.get(0).getMatchResult()); + } + + @Test + public void testCreateLinkTwiceWithDifferentGoldenResourceAndMatchResult() { + assertLinkCount(1); + + Patient patient = createPatient(buildPatientWithNameAndId("PatientGiven", "ID.PatientGiven.123"), true, false); + StringType patientId = new StringType(patient.getIdElement().getValue()); + + Patient sourcePatient = createPatient(buildPatientWithNameAndId("SourcePatientGiven", "ID.SourcePatientGiven.123"), true, false); + StringType sourcePatientId = new StringType(sourcePatient.getIdElement().getValue()); + + myMdmProvider.createLink(sourcePatientId, patientId, MATCH_RESULT, myRequestDetails); + + Patient sourcePatient2 = createPatient(buildPatientWithNameAndId("SourcePatientGiven2", "ID.SourcePatientGiven.123.2"), true, false); + StringType sourcePatientId2 = new StringType(sourcePatient2.getIdElement().getValue()); + + try { + myMdmProvider.createLink(sourcePatientId2, patientId, MATCH_RESULT, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), endsWith("Use $mdm-query-links to see more details.")); + } + } + + @Test + public void testCreateLinkTwiceWithDifferentGoldenResourceAndNoMatchResult() { + assertLinkCount(1); + + Patient patient = createPatient(buildPatientWithNameAndId("PatientGiven", "ID.PatientGiven.123"), true, false); + StringType patientId = new StringType(patient.getIdElement().getValue()); + + Patient sourcePatient = createPatient(buildPatientWithNameAndId("SourcePatientGiven", "ID.SourcePatientGiven.123"), true, false); + StringType sourcePatientId = new StringType(sourcePatient.getIdElement().getValue()); + + myMdmProvider.createLink(sourcePatientId, patientId, MATCH_RESULT, myRequestDetails); + + Patient sourcePatient2 = createPatient(buildPatientWithNameAndId("SourcePatientGiven2", "ID.SourcePatientGiven.123.2"), true, false); + StringType sourcePatientId2 = new StringType(sourcePatient2.getIdElement().getValue()); + + myMdmProvider.createLink(sourcePatientId2, patientId, NO_MATCH_RESULT, myRequestDetails); + + assertLinkCount(3); + List links = myMdmLinkDaoSvc.findMdmLinksBySourceResource(patient); + assertEquals(MdmLinkSourceEnum.MANUAL, links.get(1).getLinkSource()); + assertEquals(MdmMatchResultEnum.NO_MATCH, links.get(1).getMatchResult()); + } + + @Test + public void testCreateExistentLink() { + assertLinkCount(1); + try { + myMdmProvider.createLink(mySourcePatientId, myPatientId, MATCH_RESULT,myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), startsWith("Link already exists")); + } + assertLinkCount(1); + } + + @Test + public void testCreateIllegalResultPD() { + try { + myMdmProvider.createLink(mySourcePatientId, myPatientId, POSSIBLE_DUPLICATE_RESULT, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("$mdm-create-link illegal matchResult value 'POSSIBLE_DUPLICATE'. Must be NO_MATCH, MATCH or POSSIBLE_MATCH", e.getMessage()); + } + } + + @Test + public void testCreateIllegalFirstArg() { + try { + myMdmProvider.createLink(new StringType(""), myPatientId, MATCH_RESULT, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), endsWith(" must have form / where is the id of the resource")); + } + } + + @Test + public void testCreateIllegalSecondArg() { + try { + myMdmProvider.createLink(myPatientId, new StringType(""), MATCH_RESULT, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), endsWith(" must have form / where is the id of the resource and is the type of the resource")); + } + } + + @Test + public void testCreateStrangePatient() { + Patient patient = createPatient(); + try { + myMdmProvider.createLink(new StringType(patient.getIdElement().getValue()), myPatientId, MATCH_RESULT, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + String expectedMessage = myMessageHelper.getMessageForUnmanagedResource(); + assertEquals(expectedMessage, e.getMessage()); + } + } + + @Test + public void testExcludedGoldenResource() { + Patient patient = new Patient(); + patient.getMeta().addTag().setSystem(MdmConstants.SYSTEM_MDM_MANAGED).setCode(MdmConstants.CODE_NO_MDM_MANAGED); + createPatient(patient); + try { + myMdmProvider.createLink(mySourcePatientId, new StringType(patient.getIdElement().getValue()), MATCH_RESULT, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals(myMessageHelper.getMessageForUnsupportedSourceResource(), e.getMessage()); + } + } +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmControllerSvc.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmControllerSvc.java index fd2dd82e65d..3a545874877 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmControllerSvc.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmControllerSvc.java @@ -44,5 +44,7 @@ public interface IMdmControllerSvc { IAnyResource updateLink(String theGoldenResourceId, String theSourceResourceId, String theMatchResult, MdmTransactionContext theMdmTransactionContext); + IAnyResource createLink(String theGoldenResourceId, String theSourceResourceId, @Nullable String theMatchResult, MdmTransactionContext theMdmTransactionContext); + IBaseParameters submitMdmClearJob(List theUrls, IPrimitiveType theBatchSize, ServletRequestDetails theRequestDetails); } diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmLinkCreateSvc.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmLinkCreateSvc.java new file mode 100644 index 00000000000..b4d75c91df4 --- /dev/null +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/api/IMdmLinkCreateSvc.java @@ -0,0 +1,10 @@ +package ca.uhn.fhir.mdm.api; + +import ca.uhn.fhir.mdm.model.MdmTransactionContext; +import org.hl7.fhir.instance.model.api.IAnyResource; + +import javax.annotation.Nullable; + +public interface IMdmLinkCreateSvc { + IAnyResource createLink(IAnyResource theGoldenResource, IAnyResource theSourceResource, MdmMatchResultEnum theMatchResult, MdmTransactionContext theMdmContext); +} diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmTransactionContext.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmTransactionContext.java index 9047462d50f..8d3e2c526fc 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmTransactionContext.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/model/MdmTransactionContext.java @@ -35,6 +35,7 @@ public class MdmTransactionContext { SUBMIT_RESOURCE_TO_MDM, QUERY_LINKS, UPDATE_LINK, + CREATE_LINK, DUPLICATE_GOLDEN_RESOURCES, NOT_DUPLICATE, MERGE_GOLDEN_RESOURCES, diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/BaseMdmProvider.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/BaseMdmProvider.java index b20e790d2be..3e2fc1f4134 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/BaseMdmProvider.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/BaseMdmProvider.java @@ -38,6 +38,8 @@ import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.springframework.data.domain.Page; +import javax.annotation.Nullable; + public abstract class BaseMdmProvider { @@ -81,6 +83,22 @@ public abstract class BaseMdmProvider { validateNotNull(ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, theResourceId); } + protected void validateCreateLinkParameters(IPrimitiveType theGoldenResourceId, IPrimitiveType theResourceId, @Nullable IPrimitiveType theMatchResult) { + validateNotNull(ProviderConstants.MDM_CREATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId); + validateNotNull(ProviderConstants.MDM_CREATE_LINK_RESOURCE_ID, theResourceId); + if (theMatchResult != null) { + MdmMatchResultEnum matchResult = MdmMatchResultEnum.valueOf(theMatchResult.getValue()); + switch (matchResult) { + case NO_MATCH: + case POSSIBLE_MATCH: + case MATCH: + break; + default: + throw new InvalidRequestException(ProviderConstants.MDM_CREATE_LINK + " illegal " + ProviderConstants.MDM_CREATE_LINK_MATCH_RESULT + + " value '" + matchResult + "'. Must be " + MdmMatchResultEnum.NO_MATCH + ", " + MdmMatchResultEnum.MATCH + " or " + MdmMatchResultEnum.POSSIBLE_MATCH); + } + } + } protected MdmTransactionContext createMdmContext(RequestDetails theRequestDetails, MdmTransactionContext.OperationType theOperationType, String theResourceType) { TransactionLogMessages transactionLogMessages = TransactionLogMessages.createFromTransactionGuid(theRequestDetails.getTransactionGuid()); diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/MdmProviderDstu3Plus.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/MdmProviderDstu3Plus.java index b65427eedee..d8d1ff84960 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/MdmProviderDstu3Plus.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/provider/MdmProviderDstu3Plus.java @@ -53,6 +53,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import javax.annotation.Nonnull; @@ -180,6 +181,18 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider { ); } + @Operation(name = ProviderConstants.MDM_CREATE_LINK) + public IBaseResource createLink(@OperationParam(name = ProviderConstants.MDM_CREATE_LINK_GOLDEN_RESOURCE_ID, min = 1, max = 1) IPrimitiveType theGoldenResourceId, + @OperationParam(name = ProviderConstants.MDM_CREATE_LINK_RESOURCE_ID, min = 1, max = 1) IPrimitiveType theResourceId, + @OperationParam(name = ProviderConstants.MDM_CREATE_LINK_MATCH_RESULT, min = 0, max = 1) IPrimitiveType theMatchResult, + ServletRequestDetails theRequestDetails) { + validateCreateLinkParameters(theGoldenResourceId, theResourceId, theMatchResult); + return myMdmControllerSvc.createLink(theGoldenResourceId.getValueAsString(), theResourceId.getValue(), extractStringOrNull(theMatchResult), + createMdmContext(theRequestDetails, MdmTransactionContext.OperationType.CREATE_LINK, + getResourceType(ProviderConstants.MDM_CREATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId)) + ); + } + @Operation(name = ProviderConstants.OPERATION_MDM_CLEAR, returnParameters = { @OperationParam(name = ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, typeName = "decimal") }) diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/MessageHelper.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/MessageHelper.java index c0d7b5682bf..37168596d08 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/MessageHelper.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/util/MessageHelper.java @@ -90,4 +90,21 @@ public class MessageHelper { public String getMessageForNoLink(String theGoldenRecord, String theSourceResource) { return "No link exists between " + theGoldenRecord + " and " + theSourceResource; } + + public String getMessageForPresentLink(IAnyResource theGoldenRecord, IAnyResource theSourceResource) { + return getMessageForPresentLink(theGoldenRecord.getIdElement().toVersionless().toString(), + theSourceResource.getIdElement().toVersionless().toString()); + } + + public String getMessageForPresentLink(String theGoldenRecord, String theSourceResource) { + return "Link already exists between " + theGoldenRecord + " and " + theSourceResource + ". Use $mdm-update-link instead."; + } + + public String getMessageForMultipleGoldenRecords(IAnyResource theSourceResource) { + return getMessageForMultipleGoldenRecords(theSourceResource.getIdElement().toVersionless().toString()); + } + + public String getMessageForMultipleGoldenRecords(String theSourceResource) { + return theSourceResource + " already has matched golden resource. Use $mdm-query-links to see more details."; + } } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java index f2d27eb35b9..236841320ed 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java @@ -80,6 +80,11 @@ public class ProviderConstants { public static final String MDM_UPDATE_LINK_RESOURCE_ID = "resourceId"; public static final String MDM_UPDATE_LINK_MATCH_RESULT = "matchResult"; + public static final String MDM_CREATE_LINK = "$mdm-create-link"; + public static final String MDM_CREATE_LINK_GOLDEN_RESOURCE_ID = "goldenResourceId"; + public static final String MDM_CREATE_LINK_RESOURCE_ID = "resourceId"; + public static final String MDM_CREATE_LINK_MATCH_RESULT = "matchResult"; + public static final String MDM_QUERY_LINKS = "$mdm-query-links"; public static final String MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID = "goldenResourceId"; public static final String MDM_QUERY_LINKS_RESOURCE_ID = "resourceId";