Merge pull request #2850 from hapifhir/2849_add_new_mdm_param

Added new parameter to MDM processing
This commit is contained in:
Tadgh 2021-09-17 14:36:42 -04:00 committed by GitHub
commit e3d1fbf83f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 457 additions and 49 deletions

View File

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

View File

@ -0,0 +1,4 @@
---
type: add
issue: 2850
title: "Updated handling of MDM_AFTER_PERSISTED_RESOURCE_CHECKED pointcut to include additional MDM related info."

View File

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

View File

@ -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,11 +100,11 @@ 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:
@ -103,14 +114,23 @@ public class MdmMessageHandler implements MessageHandler {
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);
} }

View File

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

View File

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

View File

@ -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) {

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

@ -106,5 +106,8 @@ public abstract class BaseMdmHelper implements BeforeEachCallback, AfterEachCall
return channel.getQueueSizeForUnitTest(); return channel.getQueueSizeForUnitTest();
} }
public PointcutLatch getAfterMdmLatch() {
return myAfterMdmLatch;
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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