Merge pull request #2850 from hapifhir/2849_add_new_mdm_param
Added new parameter to MDM processing
This commit is contained in:
commit
e3d1fbf83f
|
@ -1986,13 +1986,17 @@ public enum Pointcut implements IPointcut {
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>ca.uhn.fhir.rest.server.messaging.ResourceOperationMessage - This parameter should not be modified as processing is complete when this hook is invoked.</li>
|
* <li>ca.uhn.fhir.rest.server.messaging.ResourceOperationMessage - This parameter should not be modified as processing is complete when this hook is invoked.</li>
|
||||||
* <li>ca.uhn.fhir.rest.server.TransactionLogMessages - This parameter is for informational messages provided by the MDM module during MDM processing.</li>
|
* <li>ca.uhn.fhir.rest.server.TransactionLogMessages - This parameter is for informational messages provided by the MDM module during MDM processing.</li>
|
||||||
|
* <li>ca.uhn.fhir.mdm.api.MdmLinkChangeEvent - Contains information about the change event, including target and golden resource IDs and the operation type.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* Hooks should return <code>void</code>.
|
* Hooks should return <code>void</code>.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
MDM_AFTER_PERSISTED_RESOURCE_CHECKED(void.class, "ca.uhn.fhir.rest.server.messaging.ResourceOperationMessage", "ca.uhn.fhir.rest.server.TransactionLogMessages"),
|
MDM_AFTER_PERSISTED_RESOURCE_CHECKED(void.class,
|
||||||
|
"ca.uhn.fhir.rest.server.messaging.ResourceOperationMessage",
|
||||||
|
"ca.uhn.fhir.rest.server.TransactionLogMessages",
|
||||||
|
"ca.uhn.fhir.mdm.api.MdmLinkEvent"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <b>Performance Tracing Hook:</b>
|
* <b>Performance Tracing Hook:</b>
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
type: add
|
||||||
|
issue: 2850
|
||||||
|
title: "Updated handling of MDM_AFTER_PERSISTED_RESOURCE_CHECKED pointcut to include additional MDM related info."
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.entity;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.mdm.api.IMdmLink;
|
||||||
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
|
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
|
||||||
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
|
@ -47,7 +48,7 @@ import java.util.Date;
|
||||||
@Table(name = "MPI_LINK", uniqueConstraints = {
|
@Table(name = "MPI_LINK", uniqueConstraints = {
|
||||||
@UniqueConstraint(name = "IDX_EMPI_PERSON_TGT", columnNames = {"PERSON_PID", "TARGET_PID"}),
|
@UniqueConstraint(name = "IDX_EMPI_PERSON_TGT", columnNames = {"PERSON_PID", "TARGET_PID"}),
|
||||||
})
|
})
|
||||||
public class MdmLink {
|
public class MdmLink implements IMdmLink {
|
||||||
public static final int VERSION_LENGTH = 16;
|
public static final int VERSION_LENGTH = 16;
|
||||||
private static final int MATCH_RESULT_LENGTH = 16;
|
private static final int MATCH_RESULT_LENGTH = 16;
|
||||||
private static final int LINK_SOURCE_LENGTH = 16;
|
private static final int LINK_SOURCE_LENGTH = 16;
|
||||||
|
|
|
@ -24,18 +24,23 @@ import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
|
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||||
|
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
|
||||||
|
import ca.uhn.fhir.jpa.mdm.svc.IMdmModelConverterSvc;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.MdmMatchLinkSvc;
|
import ca.uhn.fhir.jpa.mdm.svc.MdmMatchLinkSvc;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.MdmResourceFilteringSvc;
|
import ca.uhn.fhir.jpa.mdm.svc.MdmResourceFilteringSvc;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.candidate.TooManyCandidatesException;
|
import ca.uhn.fhir.jpa.mdm.svc.candidate.TooManyCandidatesException;
|
||||||
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
|
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
|
||||||
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
|
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
|
||||||
import ca.uhn.fhir.mdm.api.IMdmSettings;
|
import ca.uhn.fhir.mdm.api.IMdmSettings;
|
||||||
|
import ca.uhn.fhir.mdm.api.MdmLinkEvent;
|
||||||
import ca.uhn.fhir.mdm.log.Logs;
|
import ca.uhn.fhir.mdm.log.Logs;
|
||||||
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||||
import ca.uhn.fhir.rest.server.TransactionLogMessages;
|
import ca.uhn.fhir.rest.server.TransactionLogMessages;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.messaging.ResourceOperationMessage;
|
import ca.uhn.fhir.rest.server.messaging.ResourceOperationMessage;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.messaging.Message;
|
import org.springframework.messaging.Message;
|
||||||
|
@ -43,11 +48,15 @@ import org.springframework.messaging.MessageHandler;
|
||||||
import org.springframework.messaging.MessagingException;
|
import org.springframework.messaging.MessagingException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class MdmMessageHandler implements MessageHandler {
|
public class MdmMessageHandler implements MessageHandler {
|
||||||
|
|
||||||
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
|
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MdmLinkDaoSvc myMdmLinkDaoSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
private MdmMatchLinkSvc myMdmMatchLinkSvc;
|
private MdmMatchLinkSvc myMdmMatchLinkSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -58,6 +67,8 @@ public class MdmMessageHandler implements MessageHandler {
|
||||||
private MdmResourceFilteringSvc myMdmResourceFilteringSvc;
|
private MdmResourceFilteringSvc myMdmResourceFilteringSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IMdmSettings myMdmSettings;
|
private IMdmSettings myMdmSettings;
|
||||||
|
@Autowired
|
||||||
|
private IMdmModelConverterSvc myModelConverter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message<?> theMessage) throws MessagingException {
|
public void handleMessage(Message<?> theMessage) throws MessagingException {
|
||||||
|
@ -89,28 +100,37 @@ public class MdmMessageHandler implements MessageHandler {
|
||||||
try {
|
try {
|
||||||
switch (theMsg.getOperationType()) {
|
switch (theMsg.getOperationType()) {
|
||||||
case CREATE:
|
case CREATE:
|
||||||
handleCreatePatientOrPractitioner(theMsg, mdmContext);
|
handleCreateResource(theMsg, mdmContext);
|
||||||
break;
|
break;
|
||||||
case UPDATE:
|
case UPDATE:
|
||||||
case MANUALLY_TRIGGERED:
|
case MANUALLY_TRIGGERED:
|
||||||
handleUpdatePatientOrPractitioner(theMsg, mdmContext);
|
handleUpdateResource(theMsg, mdmContext);
|
||||||
break;
|
break;
|
||||||
case DELETE:
|
case DELETE:
|
||||||
default:
|
default:
|
||||||
ourLog.trace("Not processing modified message for {}", theMsg.getOperationType());
|
ourLog.trace("Not processing modified message for {}", theMsg.getOperationType());
|
||||||
}
|
}
|
||||||
}catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log(mdmContext, "Failure during MDM processing: " + e.getMessage(), e);
|
log(mdmContext, "Failure during MDM processing: " + e.getMessage(), e);
|
||||||
mdmContext.addTransactionLogMessage(e.getMessage());
|
mdmContext.addTransactionLogMessage(e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
|
|
||||||
// Interceptor call: MDM_AFTER_PERSISTED_RESOURCE_CHECKED
|
// Interceptor call: MDM_AFTER_PERSISTED_RESOURCE_CHECKED
|
||||||
ResourceOperationMessage outgoingMsg = new ResourceOperationMessage(myFhirContext, theMsg.getPayload(myFhirContext), theMsg.getOperationType());
|
IBaseResource targetResource = theMsg.getPayload(myFhirContext);
|
||||||
|
ResourceOperationMessage outgoingMsg = new ResourceOperationMessage(myFhirContext, targetResource, theMsg.getOperationType());
|
||||||
outgoingMsg.setTransactionId(theMsg.getTransactionId());
|
outgoingMsg.setTransactionId(theMsg.getTransactionId());
|
||||||
|
|
||||||
|
MdmLinkEvent linkChangeEvent = new MdmLinkEvent();
|
||||||
|
mdmContext.getMdmLinks()
|
||||||
|
.stream()
|
||||||
|
.forEach(l -> {
|
||||||
|
linkChangeEvent.addMdmLink(myModelConverter.toJson((MdmLink) l));
|
||||||
|
});
|
||||||
|
|
||||||
HookParams params = new HookParams()
|
HookParams params = new HookParams()
|
||||||
.add(ResourceOperationMessage.class, outgoingMsg)
|
.add(ResourceOperationMessage.class, outgoingMsg)
|
||||||
.add(TransactionLogMessages.class, mdmContext.getTransactionLogMessages());
|
.add(TransactionLogMessages.class, mdmContext.getTransactionLogMessages())
|
||||||
|
.add(MdmLinkEvent.class, linkChangeEvent);
|
||||||
|
|
||||||
myInterceptorBroadcaster.callHooks(Pointcut.MDM_AFTER_PERSISTED_RESOURCE_CHECKED, params);
|
myInterceptorBroadcaster.callHooks(Pointcut.MDM_AFTER_PERSISTED_RESOURCE_CHECKED, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,7 +162,7 @@ public class MdmMessageHandler implements MessageHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCreatePatientOrPractitioner(ResourceModifiedMessage theMsg, MdmTransactionContext theMdmTransactionContext) {
|
private void handleCreateResource(ResourceModifiedMessage theMsg, MdmTransactionContext theMdmTransactionContext) {
|
||||||
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(getResourceFromPayload(theMsg), theMdmTransactionContext);
|
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(getResourceFromPayload(theMsg), theMdmTransactionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +170,7 @@ public class MdmMessageHandler implements MessageHandler {
|
||||||
return (IAnyResource) theMsg.getNewPayload(myFhirContext);
|
return (IAnyResource) theMsg.getNewPayload(myFhirContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleUpdatePatientOrPractitioner(ResourceModifiedMessage theMsg, MdmTransactionContext theMdmTransactionContext) {
|
private void handleUpdateResource(ResourceModifiedMessage theMsg, MdmTransactionContext theMdmTransactionContext) {
|
||||||
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(getResourceFromPayload(theMsg), theMdmTransactionContext);
|
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(getResourceFromPayload(theMsg), theMdmTransactionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,13 +32,15 @@ import ca.uhn.fhir.jpa.mdm.dao.MdmLinkFactory;
|
||||||
import ca.uhn.fhir.jpa.mdm.interceptor.IMdmStorageInterceptor;
|
import ca.uhn.fhir.jpa.mdm.interceptor.IMdmStorageInterceptor;
|
||||||
import ca.uhn.fhir.jpa.mdm.interceptor.MdmStorageInterceptor;
|
import ca.uhn.fhir.jpa.mdm.interceptor.MdmStorageInterceptor;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.GoldenResourceMergerSvcImpl;
|
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.MdmControllerSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.MdmEidUpdateService;
|
import ca.uhn.fhir.jpa.mdm.svc.MdmEidUpdateService;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.MdmLinkQuerySvcImpl;
|
import ca.uhn.fhir.jpa.mdm.svc.MdmLinkQuerySvcImplSvc;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.MdmLinkSvcImpl;
|
import ca.uhn.fhir.jpa.mdm.svc.MdmLinkSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.MdmLinkUpdaterSvcImpl;
|
import ca.uhn.fhir.jpa.mdm.svc.MdmLinkUpdaterSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.MdmMatchFinderSvcImpl;
|
import ca.uhn.fhir.jpa.mdm.svc.MdmMatchFinderSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.MdmMatchLinkSvc;
|
import ca.uhn.fhir.jpa.mdm.svc.MdmMatchLinkSvc;
|
||||||
|
import ca.uhn.fhir.jpa.mdm.svc.MdmModelConverterSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.MdmResourceDaoSvc;
|
import ca.uhn.fhir.jpa.mdm.svc.MdmResourceDaoSvc;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.MdmResourceFilteringSvc;
|
import ca.uhn.fhir.jpa.mdm.svc.MdmResourceFilteringSvc;
|
||||||
import ca.uhn.fhir.jpa.mdm.svc.MdmSearchParamSvc;
|
import ca.uhn.fhir.jpa.mdm.svc.MdmSearchParamSvc;
|
||||||
|
@ -178,7 +180,12 @@ public class MdmConsumerConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
IMdmLinkQuerySvc mdmLinkQuerySvc() {
|
IMdmLinkQuerySvc mdmLinkQuerySvc() {
|
||||||
return new MdmLinkQuerySvcImpl();
|
return new MdmLinkQuerySvcImplSvc();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
IMdmModelConverterSvc mdmModelConverterSvc() {
|
||||||
|
return new MdmModelConverterSvcImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package ca.uhn.fhir.jpa.mdm.svc;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server - Master Data Management
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||||
|
import ca.uhn.fhir.mdm.api.MdmLinkJson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contract for decoupling API dependency from the base / JPA modules.
|
||||||
|
*/
|
||||||
|
public interface IMdmModelConverterSvc {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates JSON representation of the provided MDM link
|
||||||
|
*
|
||||||
|
* @param theLink Link to convert
|
||||||
|
* @return Returns the converted link
|
||||||
|
*/
|
||||||
|
public MdmLinkJson toJson(MdmLink theLink);
|
||||||
|
|
||||||
|
}
|
|
@ -36,20 +36,24 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.Example;
|
import org.springframework.data.domain.Example;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
|
|
||||||
public class MdmLinkQuerySvcImpl implements IMdmLinkQuerySvc {
|
public class MdmLinkQuerySvcImplSvc implements IMdmLinkQuerySvc {
|
||||||
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(MdmLinkQuerySvcImpl.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(MdmLinkQuerySvcImplSvc.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MdmLinkDaoSvc myMdmLinkDaoSvc;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
IdHelperService myIdHelperService;
|
IdHelperService myIdHelperService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
MdmLinkDaoSvc myMdmLinkDaoSvc;
|
IMdmModelConverterSvc myMdmModelConverterSvc;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<MdmLinkJson> queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest) {
|
public Page<MdmLinkJson> queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest) {
|
||||||
Example<MdmLink> exampleLink = exampleLinkFromParameters(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource);
|
Example<MdmLink> exampleLink = exampleLinkFromParameters(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource);
|
||||||
Page<MdmLink> mdmLinkByExample = myMdmLinkDaoSvc.findMdmLinkByExample(exampleLink, thePageRequest);
|
Page<MdmLink> mdmLinkByExample = myMdmLinkDaoSvc.findMdmLinkByExample(exampleLink, thePageRequest);
|
||||||
Page<MdmLinkJson> map = mdmLinkByExample.map(this::toJson);
|
Page<MdmLinkJson> map = mdmLinkByExample.map(myMdmModelConverterSvc::toJson);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,28 +61,10 @@ public class MdmLinkQuerySvcImpl implements IMdmLinkQuerySvc {
|
||||||
public Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest) {
|
public Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest) {
|
||||||
Example<MdmLink> exampleLink = exampleLinkFromParameters(null, null, MdmMatchResultEnum.POSSIBLE_DUPLICATE, null);
|
Example<MdmLink> exampleLink = exampleLinkFromParameters(null, null, MdmMatchResultEnum.POSSIBLE_DUPLICATE, null);
|
||||||
Page<MdmLink> mdmLinkPage = myMdmLinkDaoSvc.findMdmLinkByExample(exampleLink, thePageRequest);
|
Page<MdmLink> mdmLinkPage = myMdmLinkDaoSvc.findMdmLinkByExample(exampleLink, thePageRequest);
|
||||||
Page<MdmLinkJson> map = mdmLinkPage.map(this::toJson);
|
Page<MdmLinkJson> map = mdmLinkPage.map(myMdmModelConverterSvc::toJson);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MdmLinkJson toJson(MdmLink theLink) {
|
|
||||||
MdmLinkJson retval = new MdmLinkJson();
|
|
||||||
String sourceId = myIdHelperService.resourceIdFromPidOrThrowException(theLink.getSourcePid()).toVersionless().getValue();
|
|
||||||
retval.setSourceId(sourceId);
|
|
||||||
String goldenResourceId = myIdHelperService.resourceIdFromPidOrThrowException(theLink.getGoldenResourcePid()).toVersionless().getValue();
|
|
||||||
retval.setGoldenResourceId(goldenResourceId);
|
|
||||||
retval.setCreated(theLink.getCreated());
|
|
||||||
retval.setEidMatch(theLink.getEidMatch());
|
|
||||||
retval.setLinkSource(theLink.getLinkSource());
|
|
||||||
retval.setMatchResult(theLink.getMatchResult());
|
|
||||||
retval.setLinkCreatedNewResource(theLink.getHadToCreateNewGoldenResource());
|
|
||||||
retval.setScore(theLink.getScore());
|
|
||||||
retval.setUpdated(theLink.getUpdated());
|
|
||||||
retval.setVector(theLink.getVector());
|
|
||||||
retval.setVersion(theLink.getVersion());
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Example<MdmLink> exampleLinkFromParameters(IIdType theGoldenResourceId, IIdType theSourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource) {
|
private Example<MdmLink> exampleLinkFromParameters(IIdType theGoldenResourceId, IIdType theSourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource) {
|
||||||
MdmLink mdmLink = myMdmLinkDaoSvc.newMdmLink();
|
MdmLink mdmLink = myMdmLinkDaoSvc.newMdmLink();
|
||||||
if (theGoldenResourceId != null) {
|
if (theGoldenResourceId != null) {
|
|
@ -53,6 +53,8 @@ public class MdmLinkSvcImpl implements IMdmLinkSvc {
|
||||||
private MdmLinkDaoSvc myMdmLinkDaoSvc;
|
private MdmLinkDaoSvc myMdmLinkDaoSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IdHelperService myIdHelperService;
|
private IdHelperService myIdHelperService;
|
||||||
|
@Autowired
|
||||||
|
private IMdmModelConverterSvc myMdmModelConverterSvc;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
|
@ -69,7 +71,8 @@ public class MdmLinkSvcImpl implements IMdmLinkSvc {
|
||||||
validateRequestIsLegal(theGoldenResource, theSourceResource, matchResultEnum, theLinkSource);
|
validateRequestIsLegal(theGoldenResource, theSourceResource, matchResultEnum, theLinkSource);
|
||||||
|
|
||||||
myMdmResourceDaoSvc.upsertGoldenResource(theGoldenResource, theMdmTransactionContext.getResourceType());
|
myMdmResourceDaoSvc.upsertGoldenResource(theGoldenResource, theMdmTransactionContext.getResourceType());
|
||||||
createOrUpdateLinkEntity(theGoldenResource, theSourceResource, theMatchOutcome, theLinkSource, theMdmTransactionContext);
|
MdmLink link = createOrUpdateLinkEntity(theGoldenResource, theSourceResource, theMatchOutcome, theLinkSource, theMdmTransactionContext);
|
||||||
|
theMdmTransactionContext.addMdmLink(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean goldenResourceLinkedAsNoMatch(IAnyResource theGoldenResource, IAnyResource theSourceResource) {
|
private boolean goldenResourceLinkedAsNoMatch(IAnyResource theGoldenResource, IAnyResource theSourceResource) {
|
||||||
|
@ -87,6 +90,7 @@ public class MdmLinkSvcImpl implements IMdmLinkSvc {
|
||||||
MdmLink mdmLink = optionalMdmLink.get();
|
MdmLink mdmLink = optionalMdmLink.get();
|
||||||
log(theMdmTransactionContext, "Deleting MdmLink [" + theGoldenResource.getIdElement().toVersionless() + " -> " + theSourceResource.getIdElement().toVersionless() + "] with result: " + mdmLink.getMatchResult());
|
log(theMdmTransactionContext, "Deleting MdmLink [" + theGoldenResource.getIdElement().toVersionless() + " -> " + theSourceResource.getIdElement().toVersionless() + "] with result: " + mdmLink.getMatchResult());
|
||||||
myMdmLinkDaoSvc.deleteLink(mdmLink);
|
myMdmLinkDaoSvc.deleteLink(mdmLink);
|
||||||
|
theMdmTransactionContext.addMdmLink(mdmLink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,8 +133,8 @@ public class MdmLinkSvcImpl implements IMdmLinkSvc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createOrUpdateLinkEntity(IBaseResource theGoldenResource, IBaseResource theSourceResource, MdmMatchOutcome theMatchOutcome, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmTransactionContext) {
|
private MdmLink createOrUpdateLinkEntity(IBaseResource theGoldenResource, IBaseResource theSourceResource, MdmMatchOutcome theMatchOutcome, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmTransactionContext) {
|
||||||
myMdmLinkDaoSvc.createOrUpdateLinkEntity(theGoldenResource, theSourceResource, theMatchOutcome, theLinkSource, theMdmTransactionContext);
|
return myMdmLinkDaoSvc.createOrUpdateLinkEntity(theGoldenResource, theSourceResource, theMatchOutcome, theLinkSource, theMdmTransactionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void log(MdmTransactionContext theMdmTransactionContext, String theMessage) {
|
private void log(MdmTransactionContext theMdmTransactionContext, String theMessage) {
|
||||||
|
|
|
@ -99,6 +99,7 @@ public class MdmMatchLinkSvc {
|
||||||
handleMdmWithSingleCandidate(theResource, firstMatch, theMdmTransactionContext);
|
handleMdmWithSingleCandidate(theResource, firstMatch, theMdmTransactionContext);
|
||||||
} else {
|
} else {
|
||||||
log(theMdmTransactionContext, "MDM received multiple match candidates, that were linked to different Golden Resources. Setting POSSIBLE_DUPLICATES and POSSIBLE_MATCHES.");
|
log(theMdmTransactionContext, "MDM received multiple match candidates, that were linked to different Golden Resources. Setting POSSIBLE_DUPLICATES and POSSIBLE_MATCHES.");
|
||||||
|
|
||||||
//Set them all as POSSIBLE_MATCH
|
//Set them all as POSSIBLE_MATCH
|
||||||
List<IAnyResource> goldenResources = new ArrayList<>();
|
List<IAnyResource> goldenResources = new ArrayList<>();
|
||||||
for (MatchedGoldenResourceCandidate matchedGoldenResourceCandidate : theCandidateList.getCandidates()) {
|
for (MatchedGoldenResourceCandidate matchedGoldenResourceCandidate : theCandidateList.getCandidates()) {
|
||||||
|
@ -112,6 +113,7 @@ public class MdmMatchLinkSvc {
|
||||||
|
|
||||||
//Set all GoldenResources as POSSIBLE_DUPLICATE of the last GoldenResource.
|
//Set all GoldenResources as POSSIBLE_DUPLICATE of the last GoldenResource.
|
||||||
IAnyResource firstGoldenResource = goldenResources.get(0);
|
IAnyResource firstGoldenResource = goldenResources.get(0);
|
||||||
|
|
||||||
goldenResources.subList(1, goldenResources.size())
|
goldenResources.subList(1, goldenResources.size())
|
||||||
.forEach(possibleDuplicateGoldenResource -> {
|
.forEach(possibleDuplicateGoldenResource -> {
|
||||||
MdmMatchOutcome outcome = MdmMatchOutcome.POSSIBLE_DUPLICATE;
|
MdmMatchOutcome outcome = MdmMatchOutcome.POSSIBLE_DUPLICATE;
|
||||||
|
@ -138,6 +140,7 @@ public class MdmMatchLinkSvc {
|
||||||
if (myGoldenResourceHelper.isPotentialDuplicate(goldenResource, theTargetResource)) {
|
if (myGoldenResourceHelper.isPotentialDuplicate(goldenResource, theTargetResource)) {
|
||||||
log(theMdmTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
|
log(theMdmTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
|
||||||
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(theTargetResource, theMdmTransactionContext);
|
IAnyResource newGoldenResource = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(theTargetResource, theMdmTransactionContext);
|
||||||
|
|
||||||
myMdmLinkSvc.updateLink(newGoldenResource, theTargetResource, MdmMatchOutcome.NEW_GOLDEN_RESOURCE_MATCH, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
|
myMdmLinkSvc.updateLink(newGoldenResource, theTargetResource, MdmMatchOutcome.NEW_GOLDEN_RESOURCE_MATCH, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
|
||||||
myMdmLinkSvc.updateLink(newGoldenResource, goldenResource, MdmMatchOutcome.POSSIBLE_DUPLICATE, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
|
myMdmLinkSvc.updateLink(newGoldenResource, goldenResource, MdmMatchOutcome.POSSIBLE_DUPLICATE, MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
|
||||||
} else {
|
} else {
|
||||||
|
@ -145,6 +148,7 @@ public class MdmMatchLinkSvc {
|
||||||
myGoldenResourceHelper.handleExternalEidAddition(goldenResource, theTargetResource, theMdmTransactionContext);
|
myGoldenResourceHelper.handleExternalEidAddition(goldenResource, theTargetResource, theMdmTransactionContext);
|
||||||
myEidUpdateService.applySurvivorshipRulesAndSaveGoldenResource(theTargetResource, goldenResource, theMdmTransactionContext);
|
myEidUpdateService.applySurvivorshipRulesAndSaveGoldenResource(theTargetResource, goldenResource, theMdmTransactionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
myMdmLinkSvc.updateLink(goldenResource, theTargetResource, theGoldenResourceCandidate.getMatchResult(), MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
|
myMdmLinkSvc.updateLink(goldenResource, theTargetResource, theGoldenResourceCandidate.getMatchResult(), MdmLinkSourceEnum.AUTO, theMdmTransactionContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package ca.uhn.fhir.jpa.mdm.svc;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server - Master Data Management
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||||
|
import ca.uhn.fhir.mdm.api.MdmLinkJson;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
public class MdmModelConverterSvcImpl implements IMdmModelConverterSvc {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
IdHelperService myIdHelperService;
|
||||||
|
|
||||||
|
public MdmLinkJson toJson(MdmLink theLink) {
|
||||||
|
MdmLinkJson retVal = new MdmLinkJson();
|
||||||
|
String sourceId = myIdHelperService.resourceIdFromPidOrThrowException(theLink.getSourcePid()).toVersionless().getValue();
|
||||||
|
retVal.setSourceId(sourceId);
|
||||||
|
String goldenResourceId = myIdHelperService.resourceIdFromPidOrThrowException(theLink.getGoldenResourcePid()).toVersionless().getValue();
|
||||||
|
retVal.setGoldenResourceId(goldenResourceId);
|
||||||
|
retVal.setCreated(theLink.getCreated());
|
||||||
|
retVal.setEidMatch(theLink.getEidMatch());
|
||||||
|
retVal.setLinkSource(theLink.getLinkSource());
|
||||||
|
retVal.setMatchResult(theLink.getMatchResult());
|
||||||
|
retVal.setLinkCreatedNewResource(theLink.getHadToCreateNewGoldenResource());
|
||||||
|
retVal.setScore(theLink.getScore());
|
||||||
|
retVal.setUpdated(theLink.getUpdated());
|
||||||
|
retVal.setVector(theLink.getVector());
|
||||||
|
retVal.setVersion(theLink.getVersion());
|
||||||
|
retVal.setRuleCount(theLink.getRuleCount());
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -54,9 +54,9 @@ public class FindCandidateByExampleSvc extends BaseCandidateFinder {
|
||||||
private IMdmMatchFinderSvc myMdmMatchFinderSvc;
|
private IMdmMatchFinderSvc myMdmMatchFinderSvc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to find matching Golden Resources by resolving them from similar Matching target resources, where target resource
|
* Attempt to find matching Golden Resources by resolving them from similar Matching target resources. Runs MDM logic
|
||||||
* can be either Patient or Practitioner. Runs MDM logic over the existing target resources, then finds their
|
* over the existing target resources, then finds their entries in the MdmLink table, and returns all the matches
|
||||||
* entries in the MdmLink table, and returns all the matches found therein.
|
* found therein.
|
||||||
*
|
*
|
||||||
* @param theTarget the {@link IBaseResource} which we want to find candidate Golden Resources for.
|
* @param theTarget the {@link IBaseResource} which we want to find candidate Golden Resources for.
|
||||||
* @return an Optional list of {@link MatchedGoldenResourceCandidate} indicating matches.
|
* @return an Optional list of {@link MatchedGoldenResourceCandidate} indicating matches.
|
||||||
|
@ -69,8 +69,8 @@ public class FindCandidateByExampleSvc extends BaseCandidateFinder {
|
||||||
|
|
||||||
List<MatchedTarget> matchedCandidates = myMdmMatchFinderSvc.getMatchedTargets(myFhirContext.getResourceType(theTarget), theTarget);
|
List<MatchedTarget> matchedCandidates = myMdmMatchFinderSvc.getMatchedTargets(myFhirContext.getResourceType(theTarget), theTarget);
|
||||||
|
|
||||||
//Convert all possible match targets to their equivalent Golden Resources by looking up in the MdmLink table,
|
// Convert all possible match targets to their equivalent Golden Resources by looking up in the MdmLink table,
|
||||||
//while ensuring that the matches aren't in our NO_MATCH list.
|
// while ensuring that the matches aren't in our NO_MATCH list.
|
||||||
// The data flow is as follows ->
|
// The data flow is as follows ->
|
||||||
// MatchedTargetCandidate -> Golden Resource -> MdmLink -> MatchedGoldenResourceCandidate
|
// MatchedTargetCandidate -> Golden Resource -> MdmLink -> MatchedGoldenResourceCandidate
|
||||||
matchedCandidates = matchedCandidates.stream().filter(mc -> mc.isMatch() || mc.isPossibleMatch()).collect(Collectors.toList());
|
matchedCandidates = matchedCandidates.stream().filter(mc -> mc.isMatch() || mc.isPossibleMatch()).collect(Collectors.toList());
|
||||||
|
|
|
@ -325,14 +325,14 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
|
||||||
assertEquals(theExpectedCount, myMdmLinkDao.count());
|
assertEquals(theExpectedCount, myMdmLinkDao.count());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IAnyResource getGoldenResourceFromTargetResource(IAnyResource theBaseResource) {
|
protected <T extends IAnyResource> T getGoldenResourceFromTargetResource(T theBaseResource) {
|
||||||
String resourceType = theBaseResource.getIdElement().getResourceType();
|
String resourceType = theBaseResource.getIdElement().getResourceType();
|
||||||
IFhirResourceDao relevantDao = myDaoRegistry.getResourceDao(resourceType);
|
IFhirResourceDao relevantDao = myDaoRegistry.getResourceDao(resourceType);
|
||||||
|
|
||||||
Optional<MdmLink> matchedLinkForTargetPid = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(myIdHelperService.getPidOrNull(theBaseResource));
|
Optional<MdmLink> matchedLinkForTargetPid = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(myIdHelperService.getPidOrNull(theBaseResource));
|
||||||
if (matchedLinkForTargetPid.isPresent()) {
|
if (matchedLinkForTargetPid.isPresent()) {
|
||||||
Long goldenResourcePid = matchedLinkForTargetPid.get().getGoldenResourcePid();
|
Long goldenResourcePid = matchedLinkForTargetPid.get().getGoldenResourcePid();
|
||||||
return (IAnyResource) relevantDao.readByPid(new ResourcePersistentId(goldenResourcePid));
|
return (T) relevantDao.readByPid(new ResourcePersistentId(goldenResourcePid));
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,5 +106,8 @@ public abstract class BaseMdmHelper implements BeforeEachCallback, AfterEachCall
|
||||||
return channel.getQueueSizeForUnitTest();
|
return channel.getQueueSizeForUnitTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PointcutLatch getAfterMdmLatch() {
|
||||||
|
return myAfterMdmLatch;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
package ca.uhn.fhir.jpa.mdm.interceptor;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.entity.MdmLink;
|
||||||
|
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
|
||||||
|
import ca.uhn.fhir.jpa.mdm.helper.MdmHelperConfig;
|
||||||
|
import ca.uhn.fhir.jpa.mdm.helper.MdmHelperR4;
|
||||||
|
import ca.uhn.fhir.jpa.search.HapiLuceneAnalysisConfigurer;
|
||||||
|
import ca.uhn.fhir.mdm.api.IMdmLink;
|
||||||
|
import ca.uhn.fhir.mdm.api.MdmLinkEvent;
|
||||||
|
import ca.uhn.fhir.mdm.api.MdmLinkJson;
|
||||||
|
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
||||||
|
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||||
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
import ca.uhn.fhir.rest.server.messaging.ResourceOperationMessage;
|
||||||
|
import org.hibernate.dialect.H2Dialect;
|
||||||
|
import org.hibernate.search.backend.lucene.cfg.LuceneBackendSettings;
|
||||||
|
import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
|
||||||
|
import org.hibernate.search.engine.cfg.BackendSettings;
|
||||||
|
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
import org.hl7.fhir.r4.model.Practitioner;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.data.domain.Example;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.slf4j.LoggerFactory.getLogger;
|
||||||
|
|
||||||
|
@TestPropertySource(properties = {
|
||||||
|
"mdm.prevent_multiple_eids=false"
|
||||||
|
})
|
||||||
|
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
|
||||||
|
@ContextConfiguration(classes = {MdmHelperConfig.class})
|
||||||
|
public class MdmEventIT extends BaseMdmR4Test {
|
||||||
|
|
||||||
|
private static final Logger ourLog = getLogger(MdmEventIT.class);
|
||||||
|
|
||||||
|
@RegisterExtension
|
||||||
|
@Autowired
|
||||||
|
public MdmHelperR4 myMdmHelper;
|
||||||
|
@Autowired
|
||||||
|
private IdHelperService myIdHelperService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDuplicateLinkChangeEvent() throws InterruptedException {
|
||||||
|
Patient patient1 = buildJanePatient();
|
||||||
|
addExternalEID(patient1, "eid-1");
|
||||||
|
addExternalEID(patient1, "eid-11");
|
||||||
|
myMdmHelper.createWithLatch(patient1);
|
||||||
|
|
||||||
|
Patient patient2 = buildPaulPatient();
|
||||||
|
addExternalEID(patient2, "eid-2");
|
||||||
|
addExternalEID(patient2, "eid-22");
|
||||||
|
myMdmHelper.createWithLatch(patient2);
|
||||||
|
|
||||||
|
Patient patient3 = buildPaulPatient();
|
||||||
|
addExternalEID(patient3, "eid-22");
|
||||||
|
myMdmHelper.createWithLatch(patient3);
|
||||||
|
|
||||||
|
patient2.getIdentifier().clear();
|
||||||
|
addExternalEID(patient2, "eid-11");
|
||||||
|
addExternalEID(patient2, "eid-22");
|
||||||
|
|
||||||
|
myMdmHelper.updateWithLatch(patient2);
|
||||||
|
|
||||||
|
MdmLinkEvent linkChangeEvent = myMdmHelper.getAfterMdmLatch().getLatchInvocationParameterOfType(MdmLinkEvent.class);
|
||||||
|
assertNotNull(linkChangeEvent);
|
||||||
|
|
||||||
|
ourLog.info("Got event: {}", linkChangeEvent);
|
||||||
|
|
||||||
|
long expectTwoPossibleMatchesForPatientTwo = linkChangeEvent.getMdmLinks()
|
||||||
|
.stream()
|
||||||
|
.filter(l -> l.getSourceId().equals(patient2.getIdElement().toVersionless().getValueAsString()) && l.getMatchResult() == MdmMatchResultEnum.POSSIBLE_MATCH)
|
||||||
|
.count();
|
||||||
|
assertEquals(2, expectTwoPossibleMatchesForPatientTwo);
|
||||||
|
|
||||||
|
long expectOnePossibleDuplicate = linkChangeEvent.getMdmLinks()
|
||||||
|
.stream()
|
||||||
|
.filter(l -> l.getMatchResult() == MdmMatchResultEnum.POSSIBLE_DUPLICATE)
|
||||||
|
.count();
|
||||||
|
assertEquals(1, expectOnePossibleDuplicate);
|
||||||
|
|
||||||
|
List<MdmLinkJson> mdmLinkEvent = linkChangeEvent.getMdmLinks();
|
||||||
|
assertEquals(3, mdmLinkEvent.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateLinkChangeEvent() throws InterruptedException {
|
||||||
|
Practitioner pr = buildPractitionerWithNameAndId("Young", "AC-DC");
|
||||||
|
myMdmHelper.createWithLatch(pr);
|
||||||
|
|
||||||
|
ResourceOperationMessage resourceOperationMessage = myMdmHelper.getAfterMdmLatch().getLatchInvocationParameterOfType(ResourceOperationMessage.class);
|
||||||
|
assertNotNull(resourceOperationMessage);
|
||||||
|
assertEquals(pr.getId(), resourceOperationMessage.getId());
|
||||||
|
|
||||||
|
MdmLink link = getLinkByTargetId(pr);
|
||||||
|
|
||||||
|
MdmLinkEvent linkChangeEvent = myMdmHelper.getAfterMdmLatch().getLatchInvocationParameterOfType(MdmLinkEvent.class);
|
||||||
|
assertNotNull(linkChangeEvent);
|
||||||
|
|
||||||
|
assertEquals(1, linkChangeEvent.getMdmLinks().size());
|
||||||
|
MdmLinkJson l = linkChangeEvent.getMdmLinks().get(0);
|
||||||
|
assertEquals(link.getGoldenResourcePid(), new IdDt(l.getGoldenResourceId()).getIdPartAsLong());
|
||||||
|
assertEquals(link.getSourcePid(), new IdDt(l.getSourceId()).getIdPartAsLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
private MdmLink getLinkByTargetId(IBaseResource theResource) {
|
||||||
|
MdmLink example = new MdmLink();
|
||||||
|
example.setSourcePid(theResource.getIdElement().getIdPartAsLong());
|
||||||
|
return myMdmLinkDao.findAll(Example.of(example)).get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateLinkChangeEvent() throws InterruptedException {
|
||||||
|
Patient patient1 = addExternalEID(buildJanePatient(), "eid-1");
|
||||||
|
myMdmHelper.createWithLatch(patient1);
|
||||||
|
|
||||||
|
MdmLinkEvent linkChangeEvent = myMdmHelper.getAfterMdmLatch().getLatchInvocationParameterOfType(MdmLinkEvent.class);
|
||||||
|
assertNotNull(linkChangeEvent);
|
||||||
|
assertEquals(1, linkChangeEvent.getMdmLinks().size());
|
||||||
|
|
||||||
|
MdmLinkJson link = linkChangeEvent.getMdmLinks().get(0);
|
||||||
|
assertEquals(patient1.getIdElement().toVersionless().getValueAsString(), link.getSourceId());
|
||||||
|
assertEquals(getLinkByTargetId(patient1).getGoldenResourcePid(), new IdDt(link.getGoldenResourceId()).getIdPartAsLong());
|
||||||
|
assertEquals(MdmMatchResultEnum.MATCH, link.getMatchResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,7 +8,9 @@ import ca.uhn.fhir.jpa.mdm.helper.MdmHelperConfig;
|
||||||
import ca.uhn.fhir.jpa.mdm.helper.MdmHelperR4;
|
import ca.uhn.fhir.jpa.mdm.helper.MdmHelperR4;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.mdm.model.CanonicalEID;
|
import ca.uhn.fhir.mdm.model.CanonicalEID;
|
||||||
|
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
|
||||||
import ca.uhn.fhir.mdm.rules.config.MdmSettings;
|
import ca.uhn.fhir.mdm.rules.config.MdmSettings;
|
||||||
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
|
@ -26,6 +28,7 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.Example;
|
||||||
import org.springframework.test.annotation.DirtiesContext;
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
|
||||||
|
@ -67,6 +70,12 @@ public class MdmStorageInterceptorIT extends BaseMdmR4Test {
|
||||||
assertLinkCount(1);
|
assertLinkCount(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MdmLink getLinkByTargetId(IBaseResource theResource) {
|
||||||
|
MdmLink example = new MdmLink();
|
||||||
|
example.setSourcePid(theResource.getIdElement().getIdPartAsLong());
|
||||||
|
return myMdmLinkDao.findAll(Example.of(example)).get(0);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchExpandingInterceptorWorks() {
|
public void testSearchExpandingInterceptorWorks() {
|
||||||
SearchParameterMap subject = new SearchParameterMap("subject", new ReferenceParam("Patient/123").setMdmExpand(true)).setLoadSynchronous(true);
|
SearchParameterMap subject = new SearchParameterMap("subject", new ReferenceParam("Patient/123").setMdmExpand(true)).setLoadSynchronous(true);
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package ca.uhn.fhir.mdm.api;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - Master Data Management
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface IMdmLink {
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package ca.uhn.fhir.mdm.api;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - Master Data Management
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.model.api.IModelJson;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class MdmLinkEvent implements IModelJson {
|
||||||
|
|
||||||
|
private List<MdmLinkJson> myMdmLinks = new ArrayList<>();
|
||||||
|
|
||||||
|
public List<MdmLinkJson> getMdmLinks() {
|
||||||
|
return myMdmLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMdmLinks(List<MdmLinkJson> theMdmLinks) {
|
||||||
|
myMdmLinks = theMdmLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MdmLinkEvent addMdmLink(MdmLinkJson theMdmLink) {
|
||||||
|
getMdmLinks().add(theMdmLink);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MdmLinkEvent{" +
|
||||||
|
"myMdmLinks=" + myMdmLinks +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,11 +48,15 @@ public class MdmLinkJson implements IModelJson {
|
||||||
@JsonProperty("version")
|
@JsonProperty("version")
|
||||||
private String myVersion;
|
private String myVersion;
|
||||||
|
|
||||||
/** This link was created as a result of an eid match **/
|
/**
|
||||||
|
* This link was created as a result of an eid match
|
||||||
|
**/
|
||||||
@JsonProperty("eidMatch")
|
@JsonProperty("eidMatch")
|
||||||
private Boolean myEidMatch;
|
private Boolean myEidMatch;
|
||||||
|
|
||||||
/** This link created a new golden resource **/
|
/**
|
||||||
|
* This link created a new golden resource
|
||||||
|
**/
|
||||||
@JsonProperty("linkCreatedNewGoldenResource")
|
@JsonProperty("linkCreatedNewGoldenResource")
|
||||||
private Boolean myLinkCreatedNewResource;
|
private Boolean myLinkCreatedNewResource;
|
||||||
|
|
||||||
|
@ -62,6 +66,9 @@ public class MdmLinkJson implements IModelJson {
|
||||||
@JsonProperty("score")
|
@JsonProperty("score")
|
||||||
private Double myScore;
|
private Double myScore;
|
||||||
|
|
||||||
|
@JsonProperty("ruleCount")
|
||||||
|
private Long myRuleCount;
|
||||||
|
|
||||||
public String getGoldenResourceId() {
|
public String getGoldenResourceId() {
|
||||||
return myGoldenResourceId;
|
return myGoldenResourceId;
|
||||||
}
|
}
|
||||||
|
@ -160,4 +167,30 @@ public class MdmLinkJson implements IModelJson {
|
||||||
myScore = theScore;
|
myScore = theScore;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getRuleCount() {
|
||||||
|
return myRuleCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRuleCount(Long theRuleCount) {
|
||||||
|
myRuleCount = theRuleCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MdmLinkJson{" +
|
||||||
|
"myGoldenResourceId='" + myGoldenResourceId + '\'' +
|
||||||
|
", mySourceId='" + mySourceId + '\'' +
|
||||||
|
", myMatchResult=" + myMatchResult +
|
||||||
|
", myLinkSource=" + myLinkSource +
|
||||||
|
", myCreated=" + myCreated +
|
||||||
|
", myUpdated=" + myUpdated +
|
||||||
|
", myVersion='" + myVersion + '\'' +
|
||||||
|
", myEidMatch=" + myEidMatch +
|
||||||
|
", myLinkCreatedNewResource=" + myLinkCreatedNewResource +
|
||||||
|
", myVector=" + myVector +
|
||||||
|
", myScore=" + myScore +
|
||||||
|
", myRuleCount=" + myRuleCount +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,13 @@ package ca.uhn.fhir.mdm.model;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.mdm.api.IMdmLink;
|
||||||
|
import ca.uhn.fhir.mdm.api.MdmLinkEvent;
|
||||||
import ca.uhn.fhir.rest.server.TransactionLogMessages;
|
import ca.uhn.fhir.rest.server.TransactionLogMessages;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class MdmTransactionContext {
|
public class MdmTransactionContext {
|
||||||
|
|
||||||
public enum OperationType {
|
public enum OperationType {
|
||||||
|
@ -45,6 +50,8 @@ public class MdmTransactionContext {
|
||||||
|
|
||||||
private String myResourceType;
|
private String myResourceType;
|
||||||
|
|
||||||
|
private List<IMdmLink> myMdmLinkEvents = new ArrayList<>();
|
||||||
|
|
||||||
public TransactionLogMessages getTransactionLogMessages() {
|
public TransactionLogMessages getTransactionLogMessages() {
|
||||||
return myTransactionLogMessages;
|
return myTransactionLogMessages;
|
||||||
}
|
}
|
||||||
|
@ -92,4 +99,17 @@ public class MdmTransactionContext {
|
||||||
public void setResourceType(String myResourceType) {
|
public void setResourceType(String myResourceType) {
|
||||||
this.myResourceType = myResourceType;
|
this.myResourceType = myResourceType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<IMdmLink> getMdmLinks() {
|
||||||
|
return myMdmLinkEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MdmTransactionContext addMdmLink(IMdmLink theMdmLinkEvent) {
|
||||||
|
getMdmLinks().add(theMdmLinkEvent);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMdmLinks(List<IMdmLink> theMdmLinkEvents) {
|
||||||
|
myMdmLinkEvents = theMdmLinkEvents;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue