2579 partitioned support for mdm (#3485)

* committed changes discussed at tasking

* Changed the subscription message to store the request partition

* modified mdm subscription loader to create a cross partition subscription in the default partition if multitenancy is enabled

* Fix errors when running Multitenant IT for mdm

* Added partition id to mdm golden resource search and creating/updating golden resource

* added partition id column to the mdm link table

* Added partition id of the source resource to partition id column of the mdm link table

* broken build

* working -links, missing hapi tests

* Fixed the mdm link db table due to feedback

* added IT for create link mdm operation, also added changes to pass the IT

* Fixed test for create mdm links, added test for update mdm link on partitioned server

* changed mdmLink search from searchbyExample to criteriaBuilder, added partitionHelperSvc to determine partition

* added test for merge golden resource and search golden resource on partitioned server

* added unit test for not duplicate on partitioned and non-partitioned server

* added criteriabuilder search and tests

* code cleanup

* added partition support to -merge-golden-resources operation

* test cleanup

* fixed  operations with multitenancy

* added test for mdm clear operation

* added waiting for batch job to finish in mdm clear operation test

* added query to dao to support partitioned server for mdm clear operation

* resolve mistake from merge conflict

* added mdm-submit operations, also fixed paging for -query

* added partitionIds constant

* fixed build issue where there was no Code.msg in mdm partition exceptions

* code review fixes

* fix broken test, also fix a bug where query link could not query by linkSource properly

* fix test stubbing

* bump hapi version to 6.0.0-PRE10-SNAPSHOT

Co-authored-by: Ken Stevens <khstevens@gmail.com>
Co-authored-by: Steven Li <steven@smilecdr.com>
Co-authored-by: Long Ma <long@smilecdr.com>
This commit is contained in:
longma1 2022-04-06 13:48:30 -06:00 committed by GitHub
parent 523fe95820
commit 01d6e15f90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
121 changed files with 1246 additions and 266 deletions

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -25,7 +25,7 @@ public final class Msg {
/**
* IMPORTANT: Please update the following comment after you add a new code
* Last code value: 2073
* Last code value: 2075
*/
private Msg() {}

View File

@ -85,7 +85,7 @@ public class Constants {
public static final String EXTOP_VALIDATE_RESOURCE = "resource";
public static final String FORMAT_HTML = "html";
public static final String FORMAT_JSON = "json";
public static final String FORMAT_NDJSON = "ndjson";
public static final String FORMAT_NDJSON = "ndjson";
public static final String FORMAT_XML = "xml";
public static final String CT_RDF_TURTLE_LEGACY = "text/turtle";
public static final String FORMAT_TURTLE = "ttl";
@ -285,6 +285,7 @@ public class Constants {
* key will be of type {@link ca.uhn.fhir.interceptor.model.RequestPartitionId}.
*/
public static final String RESOURCE_PARTITION_ID = Constants.class.getName() + "_RESOURCE_PARTITION_ID";
public static final String PARTITION_IDS = "partitionIds";
public static final String CT_APPLICATION_GZIP = "application/gzip";
public static final String[] EMPTY_STRING_ARRAY = new String[0];
public static final String SUBSCRIPTION_MULTITYPE_PREFIX = "[";

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -3,14 +3,14 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<packaging>pom</packaging>
<name>HAPI FHIR BOM</name>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.batch.mdm.job;
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.batch.reader.BaseReverseCronologicalBatchPidReader;
import ca.uhn.fhir.jpa.dao.data.IMdmLinkDao;
import ca.uhn.fhir.jpa.searchparam.ResourceSearch;
@ -28,6 +29,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
@ -41,8 +43,13 @@ public class ReverseCronologicalBatchMdmLinkPidReader extends BaseReverseCronolo
protected Set<Long> getNextPidBatch(ResourceSearch resourceSearch) {
String resourceName = resourceSearch.getResourceName();
Pageable pageable = PageRequest.of(0, getBatchSize());
RequestPartitionId requestPartitionId = resourceSearch.getRequestPartitionId();
if (requestPartitionId.isAllPartitions()){
return new HashSet<>(myMdmLinkDao.findPidByResourceNameAndThreshold(resourceName, getCurrentHighThreshold(), pageable));
}
List<Integer> partitionIds = requestPartitionId.getPartitionIds();
//Expand out the list to handle the REDIRECT/POSSIBLE DUPLICATE ones.
return new HashSet<>(myMdmLinkDao.findPidByResourceNameAndThreshold(resourceName, getCurrentHighThreshold(), pageable));
return new HashSet<>(myMdmLinkDao.findPidByResourceNameAndThresholdAndPartitionId(resourceName, getCurrentHighThreshold(), partitionIds, pageable));
}
@Override

View File

@ -73,4 +73,7 @@ public interface IMdmLinkDao extends JpaRepository<MdmLink, Long>, IHapiFhirJpaR
@Query("SELECT ml.myId FROM MdmLink ml WHERE ml.myMdmSourceType = :resourceName AND ml.myCreated <= :highThreshold ORDER BY ml.myCreated DESC")
List<Long> findPidByResourceNameAndThreshold(@Param("resourceName") String theResourceName, @Param("highThreshold") Date theHighThreshold, Pageable thePageable);
@Query("SELECT ml.myId FROM MdmLink ml WHERE ml.myMdmSourceType = :resourceName AND ml.myCreated <= :highThreshold AND ml.myPartitionIdValue IN :partitionId ORDER BY ml.myCreated DESC")
List<Long> findPidByResourceNameAndThresholdAndPartitionId(@Param("resourceName") String theResourceName, @Param("highThreshold") Date theHighThreshold, @Param("partitionId") List<Integer> thePartitionIds, Pageable thePageable);
}

View File

@ -20,10 +20,11 @@ package ca.uhn.fhir.jpa.entity;
* #L%
*/
import ca.uhn.fhir.jpa.model.entity.BasePartitionable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.mdm.api.IMdmLink;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import org.apache.commons.lang3.builder.ToStringBuilder;
import javax.persistence.Column;
@ -52,7 +53,7 @@ import java.util.Date;
//TODO GGG revisit adding this: @UniqueConstraint(name = "IDX_EMPI_GR_TGT", columnNames = {"GOLDEN_RESOURCE_PID", "TARGET_PID"}),
//TODO GGG Should i make individual indices for PERSON/TARGET?
})
public class MdmLink implements IMdmLink {
public class MdmLink extends BasePartitionable implements IMdmLink {
public static final int VERSION_LENGTH = 16;
private static final int MATCH_RESULT_LENGTH = 16;
private static final int LINK_SOURCE_LENGTH = 16;
@ -331,6 +332,7 @@ public class MdmLink implements IMdmLink {
.append("myHadToCreateNewResource", myHadToCreateNewGoldenResource)
.append("myScore", myScore)
.append("myRuleCount", myRuleCount)
.append("myPartitionId", getPartitionId())
.toString();
}

View File

@ -49,11 +49,12 @@ import java.util.stream.Collectors;
@SuppressWarnings({"SqlNoDataSourceInspection", "SpellCheckingInspection"})
public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
// H2, Derby, MariaDB, and MySql automatically add indexes to foreign keys
public static final DriverTypeEnum[] NON_AUTOMATIC_FK_INDEX_PLATFORMS = new DriverTypeEnum[] {
DriverTypeEnum.POSTGRES_9_4, DriverTypeEnum.ORACLE_12C, DriverTypeEnum.MSSQL_2012 };
private final Set<FlagEnum> myFlags;
// H2, Derby, MariaDB, and MySql automatically add indexes to foreign keys
public static final DriverTypeEnum[] NON_AUTOMATIC_FK_INDEX_PLATFORMS = new DriverTypeEnum[]{
DriverTypeEnum.POSTGRES_9_4, DriverTypeEnum.ORACLE_12C, DriverTypeEnum.MSSQL_2012};
/**
* Constructor
@ -434,14 +435,14 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
*/
private void addIndexesForDeleteExpunge(Builder theVersion) {
theVersion.onTable( "HFJ_HISTORY_TAG")
.addIndex("20211210.2", "IDX_RESHISTTAG_RESID" )
theVersion.onTable("HFJ_HISTORY_TAG")
.addIndex("20211210.2", "IDX_RESHISTTAG_RESID")
.unique(false)
.withColumns("RES_ID");
theVersion.onTable( "HFJ_RES_VER_PROV")
.addIndex("20211210.3", "FK_RESVERPROV_RES_PID" )
theVersion.onTable("HFJ_RES_VER_PROV")
.addIndex("20211210.3", "FK_RESVERPROV_RES_PID")
.unique(false)
.withColumns("RES_PID")
.onlyAppliesToPlatforms(NON_AUTOMATIC_FK_INDEX_PLATFORMS);
@ -494,6 +495,15 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.nullable()
.type(ColumnTypeEnum.STRING, 4000);
// Add partition id column for mdm
Builder.BuilderWithTableName empiLink = version.onTable("MPI_LINK");
empiLink.addColumn("20220324.1", "PARTITION_ID")
.nullable()
.type(ColumnTypeEnum.INT);
empiLink.addColumn("20220324.2", "PARTITION_DATE")
.nullable()
.type(ColumnTypeEnum.DATE_ONLY);
}
@ -523,15 +533,15 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
// Bump ConceptMap display lengths
version.onTable("TRM_CONCEPT_MAP_GRP_ELM_TGT")
.modifyColumn("20210617.1","TARGET_DISPLAY").nullable().withType(ColumnTypeEnum.STRING, 500);
.modifyColumn("20210617.1", "TARGET_DISPLAY").nullable().withType(ColumnTypeEnum.STRING, 500);
version.onTable("TRM_CONCEPT_MAP_GRP_ELEMENT")
.modifyColumn("20210617.2", "SOURCE_DISPLAY").nullable().withType(ColumnTypeEnum.STRING, 500);
version.onTable("HFJ_BLK_EXPORT_JOB")
.modifyColumn("20210624.1","REQUEST").nonNullable().withType(ColumnTypeEnum.STRING, 1024);
.modifyColumn("20210624.1", "REQUEST").nonNullable().withType(ColumnTypeEnum.STRING, 1024);
version.onTable("HFJ_IDX_CMP_STRING_UNIQ")
.modifyColumn("20210713.1","IDX_STRING").nonNullable().withType(ColumnTypeEnum.STRING, 500);
.modifyColumn("20210713.1", "IDX_STRING").nonNullable().withType(ColumnTypeEnum.STRING, 500);
version.onTable("HFJ_RESOURCE")
.addColumn("20210720.1", "SP_CMPTOKS_PRESENT").nullable().type(ColumnTypeEnum.BOOLEAN);
@ -563,7 +573,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.addColumn("20210915.1", "EXPANDED_AT")
.nullable()
.type(ColumnTypeEnum.DATE_TIMESTAMP);
/*
* Replace CLOB columns with BLOB columns
*/
@ -579,7 +589,6 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
// HFJ_SEARCH.SEARCH_QUERY_STRING
version.onTable("HFJ_SEARCH")
.migratePostgresTextClobToBinaryClob("20211003.3", "SEARCH_QUERY_STRING");
}

View File

@ -37,6 +37,8 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.StringUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
@ -249,7 +251,7 @@ public class RequestPartitionHelperSvc implements IRequestPartitionHelperSvc {
* If the partition has both, they are validated to ensure that they correspond.
*/
@Nonnull
private RequestPartitionId validateNormalizeAndNotifyHooksForRead(@Nonnull RequestPartitionId theRequestPartitionId, RequestDetails theRequest, String theResourceType) {
private RequestPartitionId validateNormalizeAndNotifyHooksForRead(@Nonnull RequestPartitionId theRequestPartitionId, RequestDetails theRequest, @Nonnull String theResourceType) {
RequestPartitionId retVal = theRequestPartitionId;
if (retVal.getPartitionNames() != null) {
@ -260,18 +262,25 @@ public class RequestPartitionHelperSvc implements IRequestPartitionHelperSvc {
// Note: It's still possible that the partition only has a date but no name/id
if (StringUtils.isNotBlank(theResourceType)) {
validateHasPartitionPermissions(theRequest, theResourceType, retVal);
}
return retVal;
}
public void validateHasPartitionPermissions(RequestDetails theRequest, String theResourceType, RequestPartitionId theRequestPartitionId) {
if (myInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_PARTITION_SELECTED)) {
RuntimeResourceDefinition runtimeResourceDefinition = myFhirContext.getResourceDefinition(theResourceType);
RuntimeResourceDefinition runtimeResourceDefinition;
runtimeResourceDefinition = myFhirContext.getResourceDefinition(theResourceType);
HookParams params = new HookParams()
.add(RequestPartitionId.class, retVal)
.add(RequestPartitionId.class, theRequestPartitionId)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(RuntimeResourceDefinition.class, runtimeResourceDefinition);
doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_SELECTED, params);
}
return retVal;
}
private RequestPartitionId validateAndNormalizePartitionIds(RequestPartitionId theRequestPartitionId) {

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.validation;
*/
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.validation.IResourceLoader;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -35,6 +36,7 @@ public class JpaResourceLoader implements IResourceLoader {
@Override
public <T extends IBaseResource> T load(Class<T> theType, IIdType theId) throws ResourceNotFoundException {
return myDaoRegistry.getResourceDao(theType).read(theId);
SystemRequestDetails systemRequestDetails = SystemRequestDetails.forAllPartitions();
return myDaoRegistry.getResourceDao(theType).read(theId, systemRequestDetails);
}
}

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -37,6 +37,7 @@ 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.model.MdmTransactionContext;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.TransactionLogMessages;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.messaging.ResourceOperationMessage;
@ -166,7 +167,9 @@ public class MdmMessageHandler implements MessageHandler {
}
private IAnyResource getResourceFromPayload(ResourceModifiedMessage theMsg) {
return (IAnyResource) theMsg.getNewPayload(myFhirContext);
IBaseResource newPayload = theMsg.getNewPayload(myFhirContext);
newPayload.setUserData(Constants.RESOURCE_PARTITION_ID, theMsg.getPartitionId());
return (IAnyResource) newPayload;
}
private void handleUpdateResource(ResourceModifiedMessage theMsg, MdmTransactionContext theMdmTransactionContext) {

View File

@ -51,6 +51,8 @@ import ca.uhn.fhir.jpa.mdm.svc.candidate.FindCandidateByLinkSvc;
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmCandidateSearchCriteriaBuilderSvc;
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmCandidateSearchSvc;
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmGoldenResourceFindingSvc;
import ca.uhn.fhir.jpa.mdm.util.MdmPartitionHelper;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
import ca.uhn.fhir.mdm.api.IMdmControllerSvc;
import ca.uhn.fhir.mdm.api.IMdmLinkCreateSvc;
@ -244,12 +246,14 @@ public class MdmConsumerConfig {
IResourceLoader theResourceLoader,
IMdmSettings theMdmSettings,
IMdmMatchFinderSvc theMdmMatchFinderSvc,
MessageHelper messageHelper) {
MessageHelper messageHelper,
IRequestPartitionHelperSvc partitionHelperSvc) {
return new MdmControllerHelper(theFhirContext,
theResourceLoader,
theMdmMatchFinderSvc,
theMdmSettings,
messageHelper);
messageHelper,
partitionHelperSvc);
}
@Bean
@ -257,4 +261,6 @@ public class MdmConsumerConfig {
return new MdmControllerSvcImpl();
}
@Bean
MdmPartitionHelper mdmPartitionHelper() {return new MdmPartitionHelper();}
}

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.dao.index.IJpaIdHelperService;
import ca.uhn.fhir.jpa.subscription.channel.api.ChannelProducerSettings;
import ca.uhn.fhir.jpa.subscription.channel.subscription.IChannelNamer;
@ -33,7 +34,9 @@ import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.HapiExtensions;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Subscription;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
@ -88,10 +91,10 @@ public class MdmSubscriptionLoader {
synchronized void updateIfNotPresent(IBaseResource theSubscription) {
try {
mySubscriptionDao.read(theSubscription.getIdElement());
mySubscriptionDao.read(theSubscription.getIdElement(), SystemRequestDetails.forAllPartitions());
} catch (ResourceNotFoundException | ResourceGoneException e) {
ourLog.info("Creating subsription " + theSubscription.getIdElement());
mySubscriptionDao.update(theSubscription);
ourLog.info("Creating subscription " + theSubscription.getIdElement());
mySubscriptionDao.update(theSubscription, SystemRequestDetails.forAllPartitions());
}
}
@ -102,6 +105,7 @@ public class MdmSubscriptionLoader {
retval.setStatus(org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus.REQUESTED);
retval.setCriteria(theCriteria);
retval.getMeta().addTag().setSystem(MdmConstants.SYSTEM_MDM_MANAGED).setCode(MdmConstants.CODE_HAPI_MDM_MANAGED);
retval.addExtension().setUrl(HapiExtensions.EXTENSION_SUBSCRIPTION_CROSS_PARTITION).setValue(new org.hl7.fhir.dstu3.model.BooleanType().setValue(true));
org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelComponent channel = retval.getChannel();
channel.setType(org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType.MESSAGE);
channel.setEndpoint("channel:" + myChannelNamer.getChannelName(IMdmSettings.EMPI_CHANNEL_NAME, new ChannelProducerSettings()));
@ -116,6 +120,7 @@ public class MdmSubscriptionLoader {
retval.setStatus(Subscription.SubscriptionStatus.REQUESTED);
retval.setCriteria(theCriteria);
retval.getMeta().addTag().setSystem(MdmConstants.SYSTEM_MDM_MANAGED).setCode(MdmConstants.CODE_HAPI_MDM_MANAGED);
retval.addExtension().setUrl(HapiExtensions.EXTENSION_SUBSCRIPTION_CROSS_PARTITION).setValue(new BooleanType().setValue(true));
Subscription.SubscriptionChannelComponent channel = retval.getChannel();
channel.setType(Subscription.SubscriptionChannelType.MESSAGE);
channel.setEndpoint("channel:" + myChannelNamer.getChannelName(IMdmSettings.EMPI_CHANNEL_NAME, new ChannelProducerSettings()));

View File

@ -21,26 +21,42 @@ package ca.uhn.fhir.jpa.mdm.dao;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.data.IMdmLinkDao;
import ca.uhn.fhir.jpa.dao.index.IJpaIdHelperService;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.rest.api.Constants;
import org.apache.commons.collections4.CollectionUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@ -58,6 +74,8 @@ public class MdmLinkDaoSvc {
private IJpaIdHelperService myJpaIdHelperService;
@Autowired
private FhirContext myFhirContext;
@Autowired
protected EntityManager myEntityManager;
@Transactional
public MdmLink createOrUpdateLinkEntity(IBaseResource theGoldenResource, IBaseResource theSourceResource, MdmMatchOutcome theMatchOutcome, MdmLinkSourceEnum theLinkSource, @Nullable MdmTransactionContext theMdmTransactionContext) {
@ -76,6 +94,11 @@ public class MdmLinkDaoSvc {
} else {
mdmLink.setScore(theMatchOutcome.score);
}
// Add partition for the mdm link if it's available in the source resource
RequestPartitionId partitionId = (RequestPartitionId) theSourceResource.getUserData(Constants.RESOURCE_PARTITION_ID);
if (partitionId != null && partitionId.getFirstPartitionIdOrNull() != null) {
mdmLink.setPartitionId(new PartitionablePartitionId(partitionId.getFirstPartitionIdOrNull(), partitionId.getPartitionDate()));
}
String message = String.format("Creating MdmLink from %s to %s -> %s", theGoldenResource.getIdElement().toUnqualifiedVersionless(), theSourceResource.getIdElement().toUnqualifiedVersionless(), theMatchOutcome);
theMdmTransactionContext.addTransactionLogMessage(message);
@ -253,13 +276,57 @@ public class MdmLinkDaoSvc {
/**
* Given an example {@link MdmLink}, return all links from the database which match the example.
* Given a list of criteria, return all links from the database which fits the criteria provided
*
* @param theExampleLink The MDM link containing the data we would like to search for.
* @param theGoldenResourceId The resource ID of the golden resource being searched.
* @param theSourceId The resource ID of the source resource being searched.
* @param theMatchResult the {@link MdmMatchResultEnum} being searched.
* @param theLinkSource the {@link MdmLinkSourceEnum} being searched.
* @param thePageRequest the {@link MdmPageRequest} paging information
* @param thePartitionId List of partitions ID being searched, where the link's partition must be in the list.
* @return a list of {@link MdmLink} entities which match the example.
*/
public Page<MdmLink> findMdmLinkByExample(Example<MdmLink> theExampleLink, MdmPageRequest thePageRequest) {
return myMdmLinkDao.findAll(theExampleLink, thePageRequest.toPageRequest());
public PageImpl<MdmLink> executeTypedQuery(IIdType theGoldenResourceId, IIdType theSourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmPageRequest thePageRequest, List<Integer> thePartitionId) {
CriteriaBuilder criteriaBuilder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<MdmLink> criteriaQuery = criteriaBuilder.createQuery(MdmLink.class);
Root<MdmLink> from = criteriaQuery.from(MdmLink.class);
List<Predicate> andPredicates = new ArrayList<>();
if (theGoldenResourceId != null) {
Predicate goldenResourcePredicate = criteriaBuilder.equal(from.get("myGoldenResourcePid").as(Long.class), myJpaIdHelperService.getPidOrThrowException(theGoldenResourceId));
andPredicates.add(goldenResourcePredicate);
}
if (theSourceId != null) {
Predicate sourceIdPredicate = criteriaBuilder.equal(from.get("mySourcePid").as(Long.class), myJpaIdHelperService.getPidOrThrowException(theSourceId));
andPredicates.add(sourceIdPredicate);
}
if (theMatchResult != null) {
Predicate matchResultPredicate = criteriaBuilder.equal(from.get("myMatchResult").as(MdmMatchResultEnum.class), theMatchResult);
andPredicates.add(matchResultPredicate);
}
if (theLinkSource != null) {
Predicate linkSourcePredicate = criteriaBuilder.equal(from.get("myLinkSource").as(MdmLinkSourceEnum.class), theLinkSource);
andPredicates.add(linkSourcePredicate);
}
if (!CollectionUtils.isEmpty(thePartitionId)) {
Expression<Integer> exp = from.get("myPartitionId").get("myPartitionId").as(Integer.class);
Predicate linkSourcePredicate = exp.in(thePartitionId);
andPredicates.add(linkSourcePredicate);
}
Predicate finalQuery = criteriaBuilder.and(andPredicates.toArray(new Predicate[0]));
TypedQuery<MdmLink> typedQuery = myEntityManager.createQuery(criteriaQuery.where(finalQuery));
CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
countQuery.select(criteriaBuilder.count(countQuery.from(MdmLink.class)))
.where(finalQuery);
Long totalResults = myEntityManager.createQuery(countQuery).getSingleResult();
return new PageImpl<>(typedQuery.setFirstResult(thePageRequest.getOffset()).setMaxResults(thePageRequest.getCount()).getResultList(),
PageRequest.of(thePageRequest.getPage(), thePageRequest.getCount()),
totalResults);
}
/**

View File

@ -24,6 +24,7 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.dao.index.IJpaIdHelperService;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
import ca.uhn.fhir.jpa.mdm.util.MdmPartitionHelper;
import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
@ -32,6 +33,7 @@ import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
import ca.uhn.fhir.mdm.util.MessageHelper;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.slf4j.Logger;
@ -58,6 +60,10 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
IJpaIdHelperService myIdHelperService;
@Autowired
MdmResourceDaoSvc myMdmResourceDaoSvc;
@Autowired
MessageHelper myMessageHelper;
@Autowired
MdmPartitionHelper myMdmPartitionHelper;
@Override
@Transactional
@ -82,6 +88,9 @@ public class GoldenResourceMergerSvcImpl implements IGoldenResourceMergerSvc {
myMdmResourceDaoSvc.upsertGoldenResource(theToGoldenResource, resourceType);
}
// check if the golden resource and the source resource are in the same partition, throw error if not
myMdmPartitionHelper.validateResourcesInSamePartition(theFromGoldenResource, theToGoldenResource);
//Merge the links from the FROM to the TO resource. Clean up dangling links.
mergeGoldenResourceLinks(theFromGoldenResource, theToGoldenResource, toGoldenResourcePid, theMdmTransactionContext);

View File

@ -21,12 +21,14 @@ package ca.uhn.fhir.jpa.mdm.svc;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.subscription.channel.api.ChannelProducerSettings;
import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
import ca.uhn.fhir.mdm.api.IMdmChannelSubmitterSvc;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.rest.api.Constants;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
@ -50,7 +52,7 @@ public class MdmChannelSubmitterSvcImpl implements IMdmChannelSubmitterSvc {
@Override
public void submitResourceToMdmChannel(IBaseResource theResource) {
ResourceModifiedJsonMessage resourceModifiedJsonMessage = new ResourceModifiedJsonMessage();
ResourceModifiedMessage resourceModifiedMessage = new ResourceModifiedMessage(myFhirContext, theResource, ResourceModifiedMessage.OperationTypeEnum.MANUALLY_TRIGGERED);
ResourceModifiedMessage resourceModifiedMessage = new ResourceModifiedMessage(myFhirContext, theResource, ResourceModifiedMessage.OperationTypeEnum.MANUALLY_TRIGGERED, null, (RequestPartitionId) theResource.getUserData(Constants.RESOURCE_PARTITION_ID));
resourceModifiedMessage.setOperationType(ResourceModifiedMessage.OperationTypeEnum.MANUALLY_TRIGGERED);
resourceModifiedJsonMessage.setPayload(resourceModifiedMessage);
boolean success = getMdmChannelProducer().send(resourceModifiedJsonMessage);

View File

@ -21,6 +21,8 @@ package ca.uhn.fhir.jpa.mdm.svc;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
import ca.uhn.fhir.mdm.api.IMdmBatchJobSubmitterFactory;
import ca.uhn.fhir.mdm.api.IMdmControllerSvc;
@ -34,6 +36,8 @@ import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.provider.MdmControllerHelper;
import ca.uhn.fhir.mdm.provider.MdmControllerUtil;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.provider.MultiUrlProcessor;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
@ -47,7 +51,9 @@ import org.springframework.stereotype.Service;
import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* This class acts as a layer between MdmProviders and MDM services to support a REST API that's not a FHIR Operation API.
@ -69,6 +75,8 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc {
IMdmLinkCreateSvc myIMdmLinkCreateSvc;
@Autowired
IMdmBatchJobSubmitterFactory myMdmBatchJobSubmitterFactory;
@Autowired
IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
public MdmControllerSvcImpl() {
}
@ -86,11 +94,33 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc {
@Override
public Page<MdmLinkJson> queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest) {
return queryLinksFromPartitionList(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource, theMdmTransactionContext, thePageRequest, null);
}
@Override
public Page<MdmLinkJson> queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, @Nullable RequestDetails theRequestDetails) {
RequestPartitionId theReadPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null, null);
Page<MdmLinkJson> resultPage;
if (theReadPartitionId.hasPartitionIds()) {
resultPage = queryLinksFromPartitionList(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource, theMdmTransactionContext, thePageRequest, theReadPartitionId.getPartitionIds());
validateMdmQueryPermissions(theReadPartitionId, resultPage.getContent(), theRequestDetails);
}
else {
resultPage = queryLinksFromPartitionList(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource, theMdmTransactionContext, thePageRequest, null);
}
return resultPage;
}
@Override
public Page<MdmLinkJson> queryLinksFromPartitionList(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, @Nullable List<Integer> thePartitionIds) {
IIdType goldenResourceId = MdmControllerUtil.extractGoldenResourceIdDtOrNull(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId);
IIdType sourceId = MdmControllerUtil.extractSourceIdDtOrNull(ProviderConstants.MDM_QUERY_LINKS_RESOURCE_ID, theSourceResourceId);
MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult);
MdmLinkSourceEnum linkSource = MdmControllerUtil.extractLinkSourceOrNull(theLinkSource);
return myMdmLinkQuerySvc.queryLinks(goldenResourceId, sourceId, matchResult, linkSource, theMdmTransactionContext, thePageRequest);
return myMdmLinkQuerySvc.queryLinks(goldenResourceId, sourceId, matchResult, linkSource, theMdmTransactionContext, thePageRequest, thePartitionIds);
}
@Override
@ -98,6 +128,22 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc {
return myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest);
}
@Override
public Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, RequestDetails theRequestDetails) {
Page<MdmLinkJson> resultPage;
RequestPartitionId readPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null, null);
if (readPartitionId.isAllPartitions()){
resultPage = myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest);
}
else {
resultPage = myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest, readPartitionId.getPartitionIds());
}
validateMdmQueryPermissions(readPartitionId, resultPage.getContent(), theRequestDetails);
return resultPage;
}
@Override
public IAnyResource updateLink(String theGoldenResourceId, String theSourceResourceId, String theMatchResult, MdmTransactionContext theMdmTransactionContext) {
MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult);
@ -133,4 +179,16 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc {
myIMdmLinkUpdaterSvc.notDuplicateGoldenResource(goldenResource, target, theMdmTransactionContext);
}
private void validateMdmQueryPermissions(RequestPartitionId theRequestPartitionId, List<MdmLinkJson> theMdmLinkJsonList, RequestDetails theRequestDetails){
Set<String> seenResourceTypes = new HashSet<>();
for (MdmLinkJson mdmLinkJson : theMdmLinkJsonList){
IdDt idDt = new IdDt(mdmLinkJson.getSourceId());
if (!seenResourceTypes.contains(idDt.getResourceType())){
myRequestPartitionHelperSvc.validateHasPartitionPermissions(theRequestDetails, idDt.getResourceType(), theRequestPartitionId);
seenResourceTypes.add(idDt.getResourceType());
}
}
}
}

View File

@ -21,10 +21,14 @@ package ca.uhn.fhir.jpa.mdm.svc;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.dao.index.IJpaIdHelperService;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
import ca.uhn.fhir.jpa.mdm.util.MdmPartitionHelper;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.mdm.api.IMdmLinkCreateSvc;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
@ -33,6 +37,7 @@ import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
import ca.uhn.fhir.mdm.util.MessageHelper;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.slf4j.Logger;
@ -56,6 +61,8 @@ public class MdmLinkCreateSvcImpl implements IMdmLinkCreateSvc {
IMdmSettings myMdmSettings;
@Autowired
MessageHelper myMessageHelper;
@Autowired
MdmPartitionHelper myMdmPartitionHelper;
@Transactional
@Override
@ -67,6 +74,9 @@ public class MdmLinkCreateSvcImpl implements IMdmLinkCreateSvc {
Long goldenResourceId = myIdHelperService.getPidOrThrowException(theGoldenResource);
Long targetId = myIdHelperService.getPidOrThrowException(theSourceResource);
// check if the golden resource and the source resource are in the same partition, throw error if not
myMdmPartitionHelper.validateResourcesInSamePartition(theGoldenResource, theSourceResource);
Optional<MdmLink> optionalMdmLink = myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndSourceResourcePid(goldenResourceId, targetId);
if (optionalMdmLink.isPresent()) {
throw new InvalidRequestException(Msg.code(753) + myMessageHelper.getMessageForPresentLink(theGoldenResource, theSourceResource));
@ -84,6 +94,12 @@ public class MdmLinkCreateSvcImpl implements IMdmLinkCreateSvc {
} else {
mdmLink.setMatchResult(theMatchResult);
}
// Add partition for the mdm link if it doesn't exist
RequestPartitionId goldenResourcePartitionId = (RequestPartitionId) theGoldenResource.getUserData(Constants.RESOURCE_PARTITION_ID);
if (goldenResourcePartitionId != null && goldenResourcePartitionId.hasPartitionIds() && goldenResourcePartitionId.getFirstPartitionIdOrNull() != null &&
(mdmLink.getPartitionId() == null || mdmLink.getPartitionId().getPartitionId() == null)) {
mdmLink.setPartitionId(new PartitionablePartitionId(goldenResourcePartitionId.getFirstPartitionIdOrNull(), goldenResourcePartitionId.getPartitionDate()));
}
ourLog.info("Manually creating a " + theGoldenResource.getIdElement().toVersionless() + " to " + theSourceResource.getIdElement().toVersionless() + " mdm link.");
myMdmLinkDaoSvc.save(mdmLink);

View File

@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.mdm.svc;
* #L%
*/
import ca.uhn.fhir.jpa.dao.index.IJpaIdHelperService;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc;
@ -33,10 +32,11 @@ import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public class MdmLinkQuerySvcImplSvc implements IMdmLinkQuerySvc {
private static final Logger ourLog = LoggerFactory.getLogger(MdmLinkQuerySvcImplSvc.class);
@ -44,44 +44,33 @@ public class MdmLinkQuerySvcImplSvc implements IMdmLinkQuerySvc {
@Autowired
MdmLinkDaoSvc myMdmLinkDaoSvc;
@Autowired
IJpaIdHelperService myIdHelperService;
@Autowired
IMdmModelConverterSvc myMdmModelConverterSvc;
@Override
@Deprecated
@Transactional
public Page<MdmLinkJson> queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest) {
Example<MdmLink> exampleLink = exampleLinkFromParameters(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource);
Page<MdmLink> mdmLinkByExample = myMdmLinkDaoSvc.findMdmLinkByExample(exampleLink, thePageRequest);
Page<MdmLinkJson> map = mdmLinkByExample.map(myMdmModelConverterSvc::toJson);
return map;
return queryLinks(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource, theMdmContext, thePageRequest, null);
}
@Override
@Transactional
public Page<MdmLinkJson> queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest, List<Integer> thePartitionId) {
Page<MdmLink> mdmLinks = myMdmLinkDaoSvc.executeTypedQuery(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource, thePageRequest, thePartitionId);
return mdmLinks.map(myMdmModelConverterSvc::toJson);
}
@Override
@Transactional
public Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest) {
Example<MdmLink> exampleLink = exampleLinkFromParameters(null, null, MdmMatchResultEnum.POSSIBLE_DUPLICATE, null);
Page<MdmLink> mdmLinkPage = myMdmLinkDaoSvc.findMdmLinkByExample(exampleLink, thePageRequest);
Page<MdmLinkJson> map = mdmLinkPage.map(myMdmModelConverterSvc::toJson);
return map;
return getDuplicateGoldenResources(theMdmContext, thePageRequest, null);
}
private Example<MdmLink> exampleLinkFromParameters(IIdType theGoldenResourceId, IIdType theSourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource) {
MdmLink mdmLink = myMdmLinkDaoSvc.newMdmLink();
if (theGoldenResourceId != null) {
mdmLink.setGoldenResourcePid(myIdHelperService.getPidOrThrowException(theGoldenResourceId));
}
if (theSourceId != null) {
mdmLink.setSourcePid(myIdHelperService.getPidOrThrowException(theSourceId));
}
if (theMatchResult != null) {
mdmLink.setMatchResult(theMatchResult);
}
if (theLinkSource != null) {
mdmLink.setLinkSource(theLinkSource);
}
return Example.of(mdmLink);
@Override
@Transactional
public Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest, List<Integer> thePartitionId) {
Page<MdmLink> mdmLinkPage = myMdmLinkDaoSvc.executeTypedQuery(null, null, MdmMatchResultEnum.POSSIBLE_DUPLICATE, null, thePageRequest, thePartitionId);
return mdmLinkPage.map(myMdmModelConverterSvc::toJson);
}
}

View File

@ -21,6 +21,12 @@ package ca.uhn.fhir.jpa.mdm.svc;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
import ca.uhn.fhir.jpa.mdm.util.MdmPartitionHelper;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.dao.index.IJpaIdHelperService;
import ca.uhn.fhir.jpa.entity.MdmLink;
@ -35,6 +41,7 @@ import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
import ca.uhn.fhir.mdm.util.MessageHelper;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import org.hl7.fhir.instance.model.api.IAnyResource;
@ -67,6 +74,8 @@ public class MdmLinkUpdaterSvcImpl implements IMdmLinkUpdaterSvc {
MessageHelper myMessageHelper;
@Autowired
IMdmSurvivorshipService myMdmSurvivorshipService;
@Autowired
MdmPartitionHelper myMdmPartitionHelper;
@Transactional
@Override
@ -78,6 +87,9 @@ public class MdmLinkUpdaterSvcImpl implements IMdmLinkUpdaterSvc {
Long goldenResourceId = myIdHelperService.getPidOrThrowException(theGoldenResource);
Long targetId = myIdHelperService.getPidOrThrowException(theSourceResource);
// check if the golden resource and the source resource are in the same partition, throw error if not
myMdmPartitionHelper.validateResourcesInSamePartition(theGoldenResource, theSourceResource);
Optional<MdmLink> optionalMdmLink = myMdmLinkDaoSvc.getLinkByGoldenResourcePidAndSourceResourcePid(goldenResourceId, targetId);
if (!optionalMdmLink.isPresent()) {
throw new InvalidRequestException(Msg.code(738) + myMessageHelper.getMessageForNoLink(theGoldenResource, theSourceResource));
@ -92,6 +104,13 @@ public class MdmLinkUpdaterSvcImpl implements IMdmLinkUpdaterSvc {
ourLog.info("Manually updating MDM Link for " + theGoldenResource.getIdElement().toVersionless() + ", " + theSourceResource.getIdElement().toVersionless() + " from " + mdmLink.getMatchResult() + " to " + theMatchResult + ".");
mdmLink.setMatchResult(theMatchResult);
mdmLink.setLinkSource(MdmLinkSourceEnum.MANUAL);
// Add partition for the mdm link if it doesn't exist
RequestPartitionId goldenResourcePartitionId = (RequestPartitionId) theGoldenResource.getUserData(Constants.RESOURCE_PARTITION_ID);
if (goldenResourcePartitionId != null && goldenResourcePartitionId.hasPartitionIds() && goldenResourcePartitionId.getFirstPartitionIdOrNull() != null &&
(mdmLink.getPartitionId() == null || mdmLink.getPartitionId().getPartitionId() == null)) {
mdmLink.setPartitionId(new PartitionablePartitionId(goldenResourcePartitionId.getFirstPartitionIdOrNull(), goldenResourcePartitionId.getPartitionDate()));
}
myMdmLinkDaoSvc.save(mdmLink);
if (theMatchResult == MdmMatchResultEnum.MATCH) {

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.mdm.svc;
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.mdm.api.IMdmMatchFinderSvc;
import ca.uhn.fhir.mdm.api.MatchedTarget;
import ca.uhn.fhir.mdm.log.Logs;
@ -49,8 +50,8 @@ public class MdmMatchFinderSvcImpl implements IMdmMatchFinderSvc {
@Override
@Nonnull
@Transactional
public List<MatchedTarget> getMatchedTargets(String theResourceType, IAnyResource theResource) {
Collection<IAnyResource> targetCandidates = myMdmCandidateSearchSvc.findCandidates(theResourceType, theResource);
public List<MatchedTarget> getMatchedTargets(String theResourceType, IAnyResource theResource, RequestPartitionId theRequestPartitionId) {
Collection<IAnyResource> targetCandidates = myMdmCandidateSearchSvc.findCandidates(theResourceType, theResource, theRequestPartitionId);
List<MatchedTarget> matches = targetCandidates.stream()
.map(candidate -> new MatchedTarget(candidate, myMdmResourceMatcherSvc.getMatchResult(theResource, candidate)))

View File

@ -20,16 +20,16 @@ package ca.uhn.fhir.jpa.mdm.svc;
* #L%
*/
import ca.uhn.fhir.jpa.mdm.svc.candidate.CandidateList;
import ca.uhn.fhir.jpa.mdm.svc.candidate.MatchedGoldenResourceCandidate;
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmGoldenResourceFindingSvc;
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
import ca.uhn.fhir.jpa.mdm.svc.candidate.CandidateList;
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmGoldenResourceFindingSvc;
import ca.uhn.fhir.jpa.mdm.svc.candidate.MatchedGoldenResourceCandidate;
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
import ca.uhn.fhir.rest.server.TransactionLogMessages;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.slf4j.Logger;

View File

@ -20,15 +20,19 @@ package ca.uhn.fhir.jpa.mdm.svc;
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -53,10 +57,11 @@ public class MdmResourceDaoSvc {
public DaoMethodOutcome upsertGoldenResource(IAnyResource theGoldenResource, String theResourceType) {
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theResourceType);
RequestDetails requestDetails = new SystemRequestDetails().setRequestPartitionId((RequestPartitionId) theGoldenResource.getUserData(Constants.RESOURCE_PARTITION_ID));
if (theGoldenResource.getIdElement().hasIdPart()) {
return resourceDao.update(theGoldenResource);
return resourceDao.update(theGoldenResource, requestDetails);
} else {
return resourceDao.create(theGoldenResource);
return resourceDao.create(theGoldenResource, requestDetails);
}
}
@ -68,7 +73,8 @@ public class MdmResourceDaoSvc {
*/
public void removeGoldenResourceTag(IAnyResource theGoldenResource, String theResourcetype) {
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theResourcetype);
resourceDao.removeTag(theGoldenResource.getIdElement(), TagTypeEnum.TAG, MdmConstants.SYSTEM_GOLDEN_RECORD_STATUS, MdmConstants.CODE_GOLDEN_RECORD);
RequestDetails requestDetails = new SystemRequestDetails().setRequestPartitionId((RequestPartitionId) theGoldenResource.getUserData(Constants.RESOURCE_PARTITION_ID));
resourceDao.removeTag(theGoldenResource.getIdElement(), TagTypeEnum.TAG, MdmConstants.SYSTEM_GOLDEN_RECORD_STATUS, MdmConstants.CODE_GOLDEN_RECORD, requestDetails);
}
public IAnyResource readGoldenResourceByPid(ResourcePersistentId theGoldenResourcePid, String theResourceType) {
@ -76,12 +82,17 @@ public class MdmResourceDaoSvc {
return (IAnyResource) resourceDao.readByPid(theGoldenResourcePid);
}
//TODO GGG MDM address this
public Optional<IAnyResource> searchGoldenResourceByEID(String theEid, String theResourceType) {
return this.searchGoldenResourceByEID(theEid, theResourceType, null);
}
public Optional<IAnyResource> searchGoldenResourceByEID(String theEid, String theResourceType, RequestPartitionId thePartitionId) {
SearchParameterMap map = buildEidSearchParameterMap(theEid, theResourceType);
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theResourceType);
IBundleProvider search = resourceDao.search(map);
SystemRequestDetails systemRequestDetails = new SystemRequestDetails();
systemRequestDetails.setRequestPartitionId(thePartitionId);
IBundleProvider search = resourceDao.search(map, systemRequestDetails);
List<IBaseResource> resources = search.getResources(0, MAX_MATCHING_GOLDEN_RESOURCES);
if (resources.isEmpty()) {

View File

@ -27,11 +27,13 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IResultIterator;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.mdm.api.IMdmChannelSubmitterSvc;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.api.IMdmSubmitSvc;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -42,6 +44,7 @@ import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
@ -66,6 +69,9 @@ public class MdmSubmitSvcImpl implements IMdmSubmitSvc {
@Autowired
private IMdmSettings myMdmSettings;
@Autowired
private IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
public static final int DEFAULT_BUFFER_SIZE = 100;
private int myBufferSize = DEFAULT_BUFFER_SIZE;
@ -75,9 +81,9 @@ public class MdmSubmitSvcImpl implements IMdmSubmitSvc {
@Override
@Transactional
public long submitAllSourceTypesToMdm(@Nullable String theCriteria) {
public long submitAllSourceTypesToMdm(@Nullable String theCriteria, @Nonnull RequestDetails theRequestDetails) {
long submittedCount = myMdmSettings.getMdmRules().getMdmTypes().stream()
.mapToLong(type -> submitSourceResourceTypeToMdm(type, theCriteria))
.mapToLong(type -> submitSourceResourceTypeToMdm(type, theCriteria, theRequestDetails))
.sum();
return submittedCount;
@ -85,7 +91,7 @@ public class MdmSubmitSvcImpl implements IMdmSubmitSvc {
@Override
@Transactional
public long submitSourceResourceTypeToMdm(String theSourceResourceType, @Nullable String theCriteria) {
public long submitSourceResourceTypeToMdm(String theSourceResourceType, @Nullable String theCriteria, @Nonnull RequestDetails theRequestDetails) {
if (theCriteria == null) {
ourLog.info("Submitting all resources of type {} to MDM", theSourceResourceType);
} else {
@ -97,13 +103,15 @@ public class MdmSubmitSvcImpl implements IMdmSubmitSvc {
spMap.setLoadSynchronous(true);
spMap.setCount(myBufferSize);
ISearchBuilder searchBuilder = myMdmSearchParamSvc.generateSearchBuilderForType(theSourceResourceType);
return submitAllMatchingResourcesToMdmChannel(spMap, searchBuilder);
RequestPartitionId requestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, theSourceResourceType, spMap, null);
return submitAllMatchingResourcesToMdmChannel(spMap, searchBuilder, requestPartitionId);
}
private long submitAllMatchingResourcesToMdmChannel(SearchParameterMap theSpMap, ISearchBuilder theSearchBuilder) {
private long submitAllMatchingResourcesToMdmChannel(SearchParameterMap theSpMap, ISearchBuilder theSearchBuilder, RequestPartitionId theRequestPartitionId) {
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(null, UUID.randomUUID().toString());
long total = 0;
try (IResultIterator query = theSearchBuilder.createQuery(theSpMap, searchRuntimeDetails, null, RequestPartitionId.defaultPartition())) {
try (IResultIterator query = theSearchBuilder.createQuery(theSpMap, searchRuntimeDetails, null, theRequestPartitionId)) {
Collection<ResourcePersistentId> pidBatch;
do {
pidBatch = query.getNextResultBatch(myBufferSize);
@ -136,22 +144,22 @@ public class MdmSubmitSvcImpl implements IMdmSubmitSvc {
@Override
@Transactional
public long submitPractitionerTypeToMdm(@Nullable String theCriteria) {
return submitSourceResourceTypeToMdm("Practitioner", theCriteria);
public long submitPractitionerTypeToMdm(@Nullable String theCriteria, @Nonnull RequestDetails theRequestDetails) {
return submitSourceResourceTypeToMdm("Practitioner", theCriteria, theRequestDetails);
}
@Override
@Transactional
public long submitPatientTypeToMdm(@Nullable String theCriteria) {
return submitSourceResourceTypeToMdm("Patient", theCriteria);
public long submitPatientTypeToMdm(@Nullable String theCriteria, @Nonnull RequestDetails theRequestDetails) {
return submitSourceResourceTypeToMdm("Patient", theCriteria, theRequestDetails);
}
@Override
@Transactional
public long submitSourceResourceToMdm(IIdType theId) {
public long submitSourceResourceToMdm(IIdType theId, RequestDetails theRequestDetails) {
validateSourceType(theId.getResourceType());
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theId.getResourceType());
IBaseResource read = resourceDao.read(theId);
IBaseResource read = resourceDao.read(theId, theRequestDetails);
myMdmChannelSubmitterSvc.submitResourceToMdmChannel(read);
return 1;
}

View File

@ -20,9 +20,11 @@ package ca.uhn.fhir.jpa.mdm.svc.candidate;
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.mdm.svc.MdmSearchParamSvc;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
@ -50,20 +52,35 @@ public class CandidateSearcher {
*
* @param theResourceType the type of resources searched on
* @param theResourceCriteria the criteria used to search for the candidates
* @param partitionId the partition for the search
* @return Optional.empty() if >= IMdmSettings.getCandidateSearchLimit() candidates are found, otherwise
* return the bundle provider for the search results.
*/
public Optional<IBundleProvider> search(String theResourceType, String theResourceCriteria) {
public Optional<IBundleProvider> search(String theResourceType, String theResourceCriteria, RequestPartitionId partitionId) {
SearchParameterMap searchParameterMap = myMdmSearchParamSvc.mapFromCriteria(theResourceType, theResourceCriteria);
searchParameterMap.setLoadSynchronousUpTo(myMdmSettings.getCandidateSearchLimit());
IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(theResourceType);
IBundleProvider retval = resourceDao.search(searchParameterMap);
SystemRequestDetails systemRequestDetails = new SystemRequestDetails();
systemRequestDetails.setRequestPartitionId(partitionId);
IBundleProvider retval = resourceDao.search(searchParameterMap, systemRequestDetails);
if (retval.size() != null && retval.size() >= myMdmSettings.getCandidateSearchLimit()) {
return Optional.empty();
}
return Optional.of(retval);
}
/**
* Perform a search for mdm candidates.
*
* @param theResourceType the type of resources searched on
* @param theResourceCriteria the criteria used to search for the candidates
* @return Optional.empty() if >= IMdmSettings.getCandidateSearchLimit() candidates are found, otherwise
* return the bundle provider for the search results.
*/
public Optional<IBundleProvider> search(String theResourceType, String theResourceCriteria) {
return this.search(theResourceType, theResourceCriteria, RequestPartitionId.allPartitions());
}
}

View File

@ -20,11 +20,13 @@ package ca.uhn.fhir.jpa.mdm.svc.candidate;
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.mdm.svc.MdmResourceDaoSvc;
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.mdm.model.CanonicalEID;
import ca.uhn.fhir.mdm.util.EIDHelper;
import ca.uhn.fhir.jpa.mdm.svc.MdmResourceDaoSvc;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.slf4j.Logger;
@ -52,7 +54,7 @@ public class FindCandidateByEidSvc extends BaseCandidateFinder {
List<CanonicalEID> eidFromResource = myEIDHelper.getExternalEid(theBaseResource);
if (!eidFromResource.isEmpty()) {
for (CanonicalEID eid : eidFromResource) {
Optional<IAnyResource> oFoundGoldenResource = myMdmResourceDaoSvc.searchGoldenResourceByEID(eid.getValue(), theBaseResource.getIdElement().getResourceType());
Optional<IAnyResource> oFoundGoldenResource = myMdmResourceDaoSvc.searchGoldenResourceByEID(eid.getValue(), theBaseResource.getIdElement().getResourceType(), (RequestPartitionId) theBaseResource.getUserData(Constants.RESOURCE_PARTITION_ID));
if (oFoundGoldenResource.isPresent()) {
IAnyResource foundGoldenResource = oFoundGoldenResource.get();
Long pidOrNull = myIdHelperService.getPidOrNull(foundGoldenResource);

View File

@ -21,6 +21,8 @@ package ca.uhn.fhir.jpa.mdm.svc.candidate;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.index.IJpaIdHelperService;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
@ -28,6 +30,7 @@ import ca.uhn.fhir.mdm.api.IMdmMatchFinderSvc;
import ca.uhn.fhir.mdm.api.MatchedTarget;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -66,7 +69,7 @@ public class FindCandidateByExampleSvc extends BaseCandidateFinder {
List<Long> goldenResourcePidsToExclude = getNoMatchGoldenResourcePids(theTarget);
List<MatchedTarget> matchedCandidates = myMdmMatchFinderSvc.getMatchedTargets(myFhirContext.getResourceType(theTarget), theTarget);
List<MatchedTarget> matchedCandidates = myMdmMatchFinderSvc.getMatchedTargets(myFhirContext.getResourceType(theTarget), theTarget, (RequestPartitionId) theTarget.getUserData(Constants.RESOURCE_PARTITION_ID));
// 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.

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.mdm.svc.candidate;
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.dao.index.IJpaIdHelperService;
import ca.uhn.fhir.mdm.api.IMdmSettings;
@ -64,12 +66,14 @@ public class MdmCandidateSearchSvc {
/**
* Given a source resource, search for all resources that are considered an MDM match based on defined MDM rules.
*
* @param theResourceType
* @param theResource the {@link IBaseResource} we are attempting to match.
* @param theResourceType the resource type of the resource being matched
* @param theResource the {@link IBaseResource} we are attempting to match.
* @param theRequestPartitionId the {@link RequestPartitionId} representation of the partitions we are limited to when attempting to match
*
* @return the list of candidate {@link IBaseResource} which could be matches to theResource
*/
@Transactional
public Collection<IAnyResource> findCandidates(String theResourceType, IAnyResource theResource) {
public Collection<IAnyResource> findCandidates(String theResourceType, IAnyResource theResource, RequestPartitionId theRequestPartitionId) {
Map<Long, IAnyResource> matchedPidsToResources = new HashMap<>();
List<MdmFilterSearchParamJson> filterSearchParams = myMdmSettings.getMdmRules().getCandidateFilterSearchParams();
List<String> filterCriteria = buildFilterQuery(filterSearchParams, theResourceType);
@ -78,7 +82,7 @@ public class MdmCandidateSearchSvc {
//If there are zero MdmResourceSearchParamJson, we end up only making a single search, otherwise we
//must perform one search per MdmResourceSearchParamJson.
if (candidateSearchParams.isEmpty()) {
searchForIdsAndAddToMap(theResourceType, theResource, matchedPidsToResources, filterCriteria, null);
searchForIdsAndAddToMap(theResourceType, theResource, matchedPidsToResources, filterCriteria, null, theRequestPartitionId);
} else {
for (MdmResourceSearchParamJson resourceSearchParam : candidateSearchParams) {
@ -86,7 +90,7 @@ public class MdmCandidateSearchSvc {
continue;
}
searchForIdsAndAddToMap(theResourceType, theResource, matchedPidsToResources, filterCriteria, resourceSearchParam);
searchForIdsAndAddToMap(theResourceType, theResource, matchedPidsToResources, filterCriteria, resourceSearchParam, theRequestPartitionId);
}
}
//Obviously we don't want to consider the freshly added resource as a potential candidate.
@ -113,7 +117,7 @@ public class MdmCandidateSearchSvc {
* 4. Store all results in `theMatchedPidsToResources`
*/
@SuppressWarnings("rawtypes")
private void searchForIdsAndAddToMap(String theResourceType, IAnyResource theResource, Map<Long, IAnyResource> theMatchedPidsToResources, List<String> theFilterCriteria, MdmResourceSearchParamJson resourceSearchParam) {
private void searchForIdsAndAddToMap(String theResourceType, IAnyResource theResource, Map<Long, IAnyResource> theMatchedPidsToResources, List<String> theFilterCriteria, MdmResourceSearchParamJson resourceSearchParam, RequestPartitionId theRequestPartitionId) {
//1.
Optional<String> oResourceCriteria = myMdmCandidateSearchCriteriaBuilderSvc.buildResourceQueryString(theResourceType, theResource, theFilterCriteria, resourceSearchParam);
if (!oResourceCriteria.isPresent()) {
@ -123,7 +127,7 @@ public class MdmCandidateSearchSvc {
ourLog.debug("Searching for {} candidates with {}", theResourceType, resourceCriteria);
//2.
Optional<IBundleProvider> bundleProvider = myCandidateSearcher.search(theResourceType, resourceCriteria);
Optional<IBundleProvider> bundleProvider = myCandidateSearcher.search(theResourceType, resourceCriteria, theRequestPartitionId);
if (!bundleProvider.isPresent()) {
throw new TooManyCandidatesException(Msg.code(762) + "More than " + myMdmSettings.getCandidateSearchLimit() + " candidate matches found for " + resourceCriteria + ". Aborting mdm matching.");
}

View File

@ -0,0 +1,25 @@
package ca.uhn.fhir.jpa.mdm.util;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.mdm.util.MessageHelper;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MdmPartitionHelper {
@Autowired
MessageHelper myMessageHelper;
public void validateResourcesInSamePartition(IAnyResource theFromResource, IAnyResource theToResource){
RequestPartitionId fromGoldenResourcePartitionId = (RequestPartitionId) theFromResource.getUserData(Constants.RESOURCE_PARTITION_ID);
RequestPartitionId toGoldenPartitionId = (RequestPartitionId) theToResource.getUserData(Constants.RESOURCE_PARTITION_ID);
if (fromGoldenResourcePartitionId != null && toGoldenPartitionId != null && fromGoldenResourcePartitionId.hasPartitionIds() && toGoldenPartitionId.hasPartitionIds() &&
!fromGoldenResourcePartitionId.hasPartitionId(toGoldenPartitionId.getFirstPartitionIdOrNull())) {
throw new InvalidRequestException(Msg.code(2075) + myMessageHelper.getMessageForMismatchPartition(theFromResource, theToResource));
}
}
}

View File

@ -1,12 +1,12 @@
package ca.uhn.fhir.jpa.mdm;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.dao.data.IMdmLinkDao;
import ca.uhn.fhir.jpa.dao.index.IJpaIdHelperService;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.config.MdmConsumerConfig;
@ -20,6 +20,9 @@ import ca.uhn.fhir.jpa.mdm.matcher.IsPossibleLinkedTo;
import ca.uhn.fhir.jpa.mdm.matcher.IsPossibleMatchWith;
import ca.uhn.fhir.jpa.mdm.matcher.IsSameGoldenResourceAs;
import ca.uhn.fhir.jpa.mdm.svc.MdmMatchLinkSvc;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig;
@ -32,6 +35,7 @@ import ca.uhn.fhir.mdm.rules.svc.MdmResourceMatcherSvc;
import ca.uhn.fhir.mdm.util.EIDHelper;
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.param.TokenParam;
@ -74,6 +78,8 @@ import static org.slf4j.LoggerFactory.getLogger;
@ContextConfiguration(classes = {MdmSubmitterConfig.class, MdmConsumerConfig.class, TestMdmConfigR4.class, SubscriptionProcessorConfig.class})
abstract public class BaseMdmR4Test extends BaseJpaR4Test {
protected static final String PARTITION_1 = "PART-1";
protected static final String PARTITION_2 = "PART-2";
public static final String NAME_GIVEN_JANE = "Jane";
public static final String NAME_GIVEN_PAUL = "Paul";
public static final String TEST_NAME_FAMILY = "Doe";
@ -118,6 +124,10 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
private IInterceptorBroadcaster myInterceptorBroadcaster;
@Autowired
private DaoRegistry myDaoRegistry;
@Autowired
protected PartitionSettings myPartitionSettings;
@Autowired
protected IPartitionLookupSvc myPartitionLookupSvc;
@BeforeEach
public void beforeSetRequestDetails() {
@ -183,8 +193,40 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
}
@Nonnull
protected Medication createMedication(Medication theMedication) {
protected Patient createPatientOnPartition(Patient thePatient, boolean theMdmManaged, boolean isRedirect, RequestPartitionId theRequestPartitionId) {
if (theMdmManaged) {
MdmResourceUtil.setMdmManaged(thePatient);
if (isRedirect) {
MdmResourceUtil.setGoldenResourceRedirected(thePatient);
} else {
MdmResourceUtil.setGoldenResource(thePatient);
}
}
SystemRequestDetails systemRequestDetails = new SystemRequestDetails();
systemRequestDetails.setRequestPartitionId(theRequestPartitionId);
DaoMethodOutcome outcome = myPatientDao.create(thePatient, systemRequestDetails);
Patient patient = (Patient) outcome.getResource();
patient.setId(outcome.getId());
patient.setUserData(Constants.RESOURCE_PARTITION_ID, theRequestPartitionId);
return patient;
}
@Nonnull
protected Patient createPatientOnPartition(Patient thePatient, RequestPartitionId theRequestPartitionId) {
//Note that since our mdm-rules block on active=true, all patients must be active.
thePatient.setActive(true);
SystemRequestDetails systemRequestDetails = new SystemRequestDetails();
systemRequestDetails.setRequestPartitionId(theRequestPartitionId);
DaoMethodOutcome outcome = myPatientDao.create(thePatient, systemRequestDetails);
Patient patient = (Patient) outcome.getResource();
patient.setId(outcome.getId());
return patient;
}
@Nonnull
protected Medication createMedication(Medication theMedication) {
DaoMethodOutcome outcome = myMedicationDao.create(theMedication);
Medication medication = (Medication) outcome.getResource();
medication.setId(outcome.getId());
@ -307,7 +349,7 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
String resourceType = theBaseResource.getIdElement().getResourceType();
IFhirResourceDao relevantDao = myDaoRegistry.getResourceDao(resourceType);
Optional<MdmLink> matchedLinkForTargetPid = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(runInTransaction(()->myIdHelperService.getPidOrNull(theBaseResource)));
Optional<MdmLink> matchedLinkForTargetPid = myMdmLinkDaoSvc.getMatchedLinkForSourcePid(runInTransaction(() -> myIdHelperService.getPidOrNull(theBaseResource)));
if (matchedLinkForTargetPid.isPresent()) {
Long goldenResourcePid = matchedLinkForTargetPid.get().getGoldenResourcePid();
return (T) relevantDao.readByPid(new ResourcePersistentId(goldenResourcePid));
@ -337,6 +379,13 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
return thePatient;
}
protected Patient createPatientAndUpdateLinksOnPartition(Patient thePatient, RequestPartitionId theRequestPartitionId) {
thePatient = createPatientOnPartition(thePatient, theRequestPartitionId);
thePatient.setUserData(Constants.RESOURCE_PARTITION_ID, theRequestPartitionId);
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(thePatient, createContextForCreate("Patient"));
return thePatient;
}
protected Medication buildMedication(String theManufacturerReference) {
Medication medication = new Medication();
medication.setManufacturer(new Reference(theManufacturerReference));
@ -387,16 +436,27 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
return thePractitioner;
}
protected Practitioner createPractitionerAndUpdateLinksOnPartition(Practitioner thePractitioner, RequestPartitionId theRequestPartitionId) {
thePractitioner.setActive(true);
SystemRequestDetails systemRequestDetails = new SystemRequestDetails();
systemRequestDetails.setRequestPartitionId(theRequestPartitionId);
DaoMethodOutcome daoMethodOutcome = myPractitionerDao.create(thePractitioner, systemRequestDetails);
thePractitioner.setId(daoMethodOutcome.getId());
thePractitioner.setUserData(Constants.RESOURCE_PARTITION_ID, theRequestPartitionId);
myMdmMatchLinkSvc.updateMdmLinksForMdmSource(thePractitioner, createContextForCreate("Practitioner"));
return thePractitioner;
}
private Matcher<IAnyResource> wrapMatcherInTransaction(Supplier<Matcher<IAnyResource>> theFunction) {
return new Matcher<IAnyResource>() {
@Override
public boolean matches(Object actual) {
return runInTransaction(()->theFunction.get().matches(actual));
return runInTransaction(() -> theFunction.get().matches(actual));
}
@Override
public void describeMismatch(Object actual, Description mismatchDescription) {
runInTransaction(()->theFunction.get().describeMismatch(actual, mismatchDescription));
runInTransaction(() -> theFunction.get().describeMismatch(actual, mismatchDescription));
}
@Override
@ -406,33 +466,33 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
@Override
public void describeTo(Description description) {
runInTransaction(()->theFunction.get().describeTo(description));
runInTransaction(() -> theFunction.get().describeTo(description));
}
};
}
protected Matcher<IAnyResource> sameGoldenResourceAs(IAnyResource... theBaseResource) {
return wrapMatcherInTransaction(()->IsSameGoldenResourceAs.sameGoldenResourceAs(myIdHelperService, myMdmLinkDaoSvc, theBaseResource));
return wrapMatcherInTransaction(() -> IsSameGoldenResourceAs.sameGoldenResourceAs(myIdHelperService, myMdmLinkDaoSvc, theBaseResource));
}
protected Matcher<IAnyResource> linkedTo(IAnyResource... theBaseResource) {
return wrapMatcherInTransaction(()->IsLinkedTo.linkedTo(myIdHelperService, myMdmLinkDaoSvc, theBaseResource));
return wrapMatcherInTransaction(() -> IsLinkedTo.linkedTo(myIdHelperService, myMdmLinkDaoSvc, theBaseResource));
}
protected Matcher<IAnyResource> possibleLinkedTo(IAnyResource... theBaseResource) {
return wrapMatcherInTransaction(()->IsPossibleLinkedTo.possibleLinkedTo(myIdHelperService, myMdmLinkDaoSvc, theBaseResource));
return wrapMatcherInTransaction(() -> IsPossibleLinkedTo.possibleLinkedTo(myIdHelperService, myMdmLinkDaoSvc, theBaseResource));
}
protected Matcher<IAnyResource> possibleMatchWith(IAnyResource... theBaseResource) {
return wrapMatcherInTransaction(()->IsPossibleMatchWith.possibleMatchWith(myIdHelperService, myMdmLinkDaoSvc, theBaseResource));
return wrapMatcherInTransaction(() -> IsPossibleMatchWith.possibleMatchWith(myIdHelperService, myMdmLinkDaoSvc, theBaseResource));
}
protected Matcher<IAnyResource> possibleDuplicateOf(IAnyResource... theBaseResource) {
return wrapMatcherInTransaction(()->IsPossibleDuplicateOf.possibleDuplicateOf(myIdHelperService, myMdmLinkDaoSvc, theBaseResource));
return wrapMatcherInTransaction(() -> IsPossibleDuplicateOf.possibleDuplicateOf(myIdHelperService, myMdmLinkDaoSvc, theBaseResource));
}
protected Matcher<IAnyResource> matchedToAGoldenResource() {
return wrapMatcherInTransaction(()->IsMatchedToAGoldenResource.matchedToAGoldenResource(myIdHelperService, myMdmLinkDaoSvc));
return wrapMatcherInTransaction(() -> IsMatchedToAGoldenResource.matchedToAGoldenResource(myIdHelperService, myMdmLinkDaoSvc));
}
protected Patient getOnlyGoldenPatient() {
@ -458,7 +518,8 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
map.setLoadSynchronous(true);
//TODO GGG ensure that this tag search works effectively.
map.add("_tag", new TokenParam(MdmConstants.SYSTEM_GOLDEN_RECORD_STATUS, theCode));
IBundleProvider bundle = myPatientDao.search(map);
SystemRequestDetails systemRequestDetails = SystemRequestDetails.forAllPartitions();
IBundleProvider bundle = myPatientDao.search(map, systemRequestDetails);
return bundle.getResources(0, 999);
}

View File

@ -1,19 +1,40 @@
package ca.uhn.fhir.jpa.mdm.config;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.subscription.channel.subscription.IChannelNamer;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.rules.json.MdmRulesJson;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.ExtensionUtil;
import ca.uhn.fhir.util.HapiExtensions;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Subscription;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@ -24,6 +45,18 @@ class MdmSubscriptionLoaderTest {
@Mock
IFhirResourceDao<IBaseResource> mySubscriptionDao;
@Mock
DaoRegistry myDaoRegistry;
@Mock
IMdmSettings myMdmSettings;
@Spy
FhirContext myFhirContext = FhirContext.forR4Cached();
@Mock
IChannelNamer myChannelNamer;
@InjectMocks
MdmSubscriptionLoader mySvc = new MdmSubscriptionLoader();
@ -37,9 +70,9 @@ class MdmSubscriptionLoaderTest {
Subscription subscription = new Subscription();
IdType id = new IdType("2401");
subscription.setIdElement(id);
when(mySubscriptionDao.read(id)).thenThrow(new ResourceGoneException(""));
when(mySubscriptionDao.read(eq(id), any())).thenThrow(new ResourceGoneException(""));
mySvc.updateIfNotPresent(subscription);
verify(mySubscriptionDao).update(subscription);
verify(mySubscriptionDao).update(eq(subscription), any(RequestDetails.class));
}
@Test
@ -47,9 +80,9 @@ class MdmSubscriptionLoaderTest {
Subscription subscription = new Subscription();
IdType id = new IdType("2401");
subscription.setIdElement(id);
when(mySubscriptionDao.read(id)).thenThrow(new ResourceNotFoundException(""));
when(mySubscriptionDao.read(eq(id), any())).thenThrow(new ResourceNotFoundException(""));
mySvc.updateIfNotPresent(subscription);
verify(mySubscriptionDao).update(subscription);
verify(mySubscriptionDao).update(eq(subscription), any(RequestDetails.class));
}
@Test
@ -57,8 +90,29 @@ class MdmSubscriptionLoaderTest {
Subscription subscription = new Subscription();
IdType id = new IdType("2401");
subscription.setIdElement(id);
when(mySubscriptionDao.read(id)).thenReturn(subscription);
when(mySubscriptionDao.read(eq(id), any())).thenReturn(subscription);
mySvc.updateIfNotPresent(subscription);
verify(mySubscriptionDao, never()).update(any());
verify(mySubscriptionDao, never()).update(any(), any(RequestDetails.class));
}
@Test
public void testDaoUpdateMdmSubscriptions() {
MdmRulesJson mdmRulesJson = new MdmRulesJson();
mdmRulesJson.setMdmTypes(Arrays.asList("Patient"));
when(myMdmSettings.getMdmRules()).thenReturn(mdmRulesJson);
when(myChannelNamer.getChannelName(any(), any())).thenReturn("Test");
when(myDaoRegistry.getResourceDao(eq("Subscription"))).thenReturn(mySubscriptionDao);
when(mySubscriptionDao.read(any(), any())).thenThrow(new ResourceGoneException(""));
mySvc.daoUpdateMdmSubscriptions();
ArgumentCaptor<Subscription> subscriptionCaptor = ArgumentCaptor.forClass(Subscription.class);
verify(mySubscriptionDao).update(subscriptionCaptor.capture(), any(RequestDetails.class));
IBaseExtension extension = ExtensionUtil.getExtensionByUrl(subscriptionCaptor.getValue(), HapiExtensions.EXTENSION_SUBSCRIPTION_CROSS_PARTITION);
assertNotNull(extension);
assertTrue(((BooleanType)extension.getValue()).booleanValue());
}
}

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.mdm.provider;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import org.hl7.fhir.instance.model.api.IAnyResource;
@ -59,6 +60,7 @@ public abstract class BaseLinkR4Test extends BaseProviderR4Test {
@AfterEach
public void after() throws IOException {
myDaoConfig.setExpungeEnabled(new DaoConfig().isExpungeEnabled());
myPartitionSettings.setPartitioningEnabled(new PartitionSettings().isPartitioningEnabled());
super.after();
}

View File

@ -27,7 +27,7 @@ import java.util.ArrayList;
import java.util.List;
public abstract class BaseProviderR4Test extends BaseMdmR4Test {
MdmProviderDstu3Plus myMdmProvider;
protected MdmProviderDstu3Plus myMdmProvider;
@Autowired
private IMdmControllerSvc myMdmControllerSvc;
@Autowired

View File

@ -1,6 +1,9 @@
package ca.uhn.fhir.jpa.mdm.provider;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
@ -17,7 +20,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
public class MdmProviderCreateLinkR4Test extends BaseLinkR4Test {
@ -43,6 +46,53 @@ public class MdmProviderCreateLinkR4Test extends BaseLinkR4Test {
assertEquals(MdmMatchResultEnum.MATCH, links.get(0).getMatchResult());
}
@Test
public void testCreateLinkWithMatchResultOnSamePartition() {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(1).setName(PARTITION_1));
assertLinkCount(1);
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionId(1);
Patient patient = createPatientOnPartition(buildPatientWithNameAndId("PatientGiven", "ID.PatientGiven.123"), true, false, requestPartitionId);
StringType patientId = new StringType(patient.getIdElement().getValue());
Patient sourcePatient = createPatientOnPartition(buildPatientWithNameAndId("SourcePatientGiven", "ID.SourcePatientGiven.123"), true, false, requestPartitionId);
StringType sourcePatientId = new StringType(sourcePatient.getIdElement().getValue());
myMdmProvider.createLink(sourcePatientId, patientId, MATCH_RESULT, myRequestDetails);
assertLinkCount(2);
List<MdmLink> links = myMdmLinkDaoSvc.findMdmLinksBySourceResource(patient);
assertEquals(links.size(), 1);
assertEquals(MdmLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
assertEquals(MdmMatchResultEnum.MATCH, links.get(0).getMatchResult());
assertNotNull(links.get(0).getPartitionId());
assertEquals(1, links.get(0).getPartitionId().getPartitionId());
}
@Test
public void testCreateLinkWithMatchResultOnDifferentPartitions() {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(1).setName(PARTITION_1));
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(2).setName(PARTITION_2));
assertLinkCount(1);
RequestPartitionId requestPartitionId1 = RequestPartitionId.fromPartitionId(1);
Patient patient = createPatientOnPartition(buildPatientWithNameAndId("PatientGiven", "ID.PatientGiven.123"), true, false, requestPartitionId1);
StringType patientId = new StringType(patient.getIdElement().getValue());
RequestPartitionId requestPartitionId2 = RequestPartitionId.fromPartitionId(2);
Patient sourcePatient = createPatientOnPartition(buildPatientWithNameAndId("SourcePatientGiven", "ID.SourcePatientGiven.123"), true, false, requestPartitionId2);
StringType sourcePatientId = new StringType(sourcePatient.getIdElement().getValue());
try {
myMdmProvider.createLink(sourcePatientId, patientId, MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), endsWith("This operation is only available for resources on the same partition."));
}
}
@Test
public void testCreateLinkWithNullMatchResult() {
assertLinkCount(1);

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.mdm.provider;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.mdm.api.MdmConstants;
import com.google.common.collect.Ordering;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -40,7 +41,7 @@ public class MdmProviderMatchR4Test extends BaseProviderR4Test {
Patient createdJane = createPatient(jane);
Patient newJane = buildJanePatient();
Bundle result = (Bundle) myMdmProvider.match(newJane);
Bundle result = (Bundle) myMdmProvider.match(newJane, new SystemRequestDetails());
assertEquals(1, result.getEntry().size());
Bundle.BundleEntryComponent entry0 = result.getEntry().get(0);
@ -64,7 +65,7 @@ public class MdmProviderMatchR4Test extends BaseProviderR4Test {
Medication createdMedication = createMedication(medication);
Medication newMedication = buildMedication("Organization/mfr");
Bundle result = (Bundle) myMdmProvider.serverMatch(newMedication, new StringType("Medication"));
Bundle result = (Bundle) myMdmProvider.serverMatch(newMedication, new StringType("Medication"), new SystemRequestDetails());
assertEquals(1, result.getEntry().size());
Bundle.BundleEntryComponent entry0 = result.getEntry().get(0);
@ -89,7 +90,7 @@ public class MdmProviderMatchR4Test extends BaseProviderR4Test {
Patient createdJane = createPatient(jane);
Patient newJane = buildJanePatient();
Bundle result = (Bundle) myMdmProvider.serverMatch(newJane, new StringType("Patient"));
Bundle result = (Bundle) myMdmProvider.serverMatch(newJane, new StringType("Patient"), new SystemRequestDetails());
assertEquals(1, result.getEntry().size());
Bundle.BundleEntryComponent entry0 = result.getEntry().get(0);
@ -115,7 +116,7 @@ public class MdmProviderMatchR4Test extends BaseProviderR4Test {
Patient newJane = buildJanePatient();
Bundle result = (Bundle) myMdmProvider.match(newJane);
Bundle result = (Bundle) myMdmProvider.match(newJane, new SystemRequestDetails());
assertEquals(2, result.getEntry().size());
Bundle.BundleEntryComponent entry0 = result.getEntry().get(0);
@ -139,7 +140,7 @@ public class MdmProviderMatchR4Test extends BaseProviderR4Test {
Patient paul = buildPaulPatient();
paul.setActive(true);
Bundle result = (Bundle) myMdmProvider.match(paul);
Bundle result = (Bundle) myMdmProvider.match(paul, new SystemRequestDetails());
assertEquals(0, result.getEntry().size());
}
@ -151,7 +152,7 @@ public class MdmProviderMatchR4Test extends BaseProviderR4Test {
Patient createdJane = createPatient(jane);
Patient newJane = buildJanePatient();
Bundle result = (Bundle) myMdmProvider.match(newJane);
Bundle result = (Bundle) myMdmProvider.match(newJane, new SystemRequestDetails());
assertEquals(1, result.getEntry().size());
assertEquals(createdJane.getId(), result.getEntryFirstRep().getResource().getId());
}
@ -221,7 +222,7 @@ public class MdmProviderMatchR4Test extends BaseProviderR4Test {
"}";
IBaseResource coarseResource = myFhirContext.newJsonParser().parseResource(coarsePatient);
Bundle result = (Bundle) myMdmProvider.match((Patient) coarseResource);
Bundle result = (Bundle) myMdmProvider.match((Patient) coarseResource, new SystemRequestDetails());
assertEquals(1, result.getEntry().size());
}
}

View File

@ -1,7 +1,10 @@
package ca.uhn.fhir.jpa.mdm.provider;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
@ -10,15 +13,19 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.TerserUtil;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@ -40,6 +47,11 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
myToGoldenPatientId = new StringType(myToGoldenPatient.getIdElement().getValue());
}
@AfterEach
public void after() throws IOException {
myPartitionSettings.setPartitioningEnabled(new PartitionSettings().isPartitioningEnabled());
super.after();
}
@Test
public void testMergeWithOverride() {
@ -69,7 +81,7 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
// we do not check setActive anymore - as not all types support that
assertTrue(MdmResourceUtil.isGoldenRecord(mergedSourcePatient));
assertTrue(!MdmResourceUtil.isGoldenRecordRedirected(mergedSourcePatient));
assertFalse(MdmResourceUtil.isGoldenRecordRedirected(mergedSourcePatient));
assertEquals(myToGoldenPatient.getIdElement(), mergedSourcePatient.getIdElement());
assertThat(mergedSourcePatient, is(sameGoldenResourceAs(myToGoldenPatient)));
@ -78,7 +90,7 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
Patient fromSourcePatient = myPatientDao.read(myFromGoldenPatient.getIdElement().toUnqualifiedVersionless());
assertTrue(!MdmResourceUtil.isGoldenRecord(fromSourcePatient));
assertFalse(MdmResourceUtil.isGoldenRecord(fromSourcePatient));
assertTrue(MdmResourceUtil.isGoldenRecordRedirected(fromSourcePatient));
//TODO GGG eventually this will need to check a redirect... this is a hack which doesnt work
@ -95,6 +107,58 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
assertEquals(link.getLinkSource(), MdmLinkSourceEnum.MANUAL);
}
@Test
public void testMergeOnSamePartition() {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(1).setName(PARTITION_1));
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionId(1);
Patient fromGoldenPatient = createPatientOnPartition(new Patient(), true, false, requestPartitionId);
StringType fromGoldenPatientId = new StringType(fromGoldenPatient.getIdElement().getValue());
Patient toGoldenPatient = createPatientOnPartition(new Patient(), true, false, requestPartitionId);
StringType toGoldenPatientId = new StringType(toGoldenPatient.getIdElement().getValue());
Patient mergedSourcePatient = (Patient) myMdmProvider.mergeGoldenResources(fromGoldenPatientId,
toGoldenPatientId, null, myRequestDetails);
assertTrue(MdmResourceUtil.isGoldenRecord(mergedSourcePatient));
assertFalse(MdmResourceUtil.isGoldenRecordRedirected(mergedSourcePatient));
assertEquals(toGoldenPatient.getIdElement(), mergedSourcePatient.getIdElement());
assertThat(mergedSourcePatient, is(sameGoldenResourceAs(toGoldenPatient)));
assertEquals(1, getAllRedirectedGoldenPatients().size());
// 2 from the set-up and only one from this test should be golden resource
assertEquals(3, getAllGoldenPatients().size());
List<MdmLink> links = myMdmLinkDaoSvc.findMdmLinksBySourceResource(fromGoldenPatient);
assertThat(links, hasSize(1));
MdmLink link = links.get(0);
assertEquals(link.getSourcePid(), fromGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
assertEquals(link.getGoldenResourcePid(), toGoldenPatient.getIdElement().toUnqualifiedVersionless().getIdPartAsLong());
assertEquals(link.getMatchResult(), MdmMatchResultEnum.REDIRECT);
assertEquals(link.getLinkSource(), MdmLinkSourceEnum.MANUAL);
}
@Test
public void testMergeOnDifferentPartitions() {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(1).setName(PARTITION_1));
RequestPartitionId requestPartitionId1 = RequestPartitionId.fromPartitionId(1);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(2).setName(PARTITION_2));
RequestPartitionId requestPartitionId2 = RequestPartitionId.fromPartitionId(2);
Patient fromGoldenPatient = createPatientOnPartition(new Patient(), true, false, requestPartitionId1);
StringType fromGoldenPatientId = new StringType(fromGoldenPatient.getIdElement().getValue());
Patient toGoldenPatient = createPatientOnPartition(new Patient(), true, false, requestPartitionId2);
StringType toGoldenPatientId = new StringType(toGoldenPatient.getIdElement().getValue());
try {
myMdmProvider.mergeGoldenResources(fromGoldenPatientId, toGoldenPatientId, null, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), endsWith("This operation is only available for resources on the same partition."));
}
}
@Test
public void testMergeWithManualOverride() {
Patient patient = TerserUtil.clone(myFhirContext, myFromGoldenPatient);
@ -109,7 +173,7 @@ public class MdmProviderMergeGoldenResourcesR4Test extends BaseProviderR4Test {
assertEquals(1, getAllGoldenPatients().size());
Patient fromSourcePatient = myPatientDao.read(myFromGoldenPatient.getIdElement().toUnqualifiedVersionless());
assertTrue(!MdmResourceUtil.isGoldenRecord(fromSourcePatient));
assertFalse(MdmResourceUtil.isGoldenRecord(fromSourcePatient));
assertTrue(MdmResourceUtil.isGoldenRecordRedirected(fromSourcePatient));
List<MdmLink> links = myMdmLinkDaoSvc.findMdmLinksBySourceResource(myFromGoldenPatient);

View File

@ -0,0 +1,125 @@
package ca.uhn.fhir.jpa.mdm.provider;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class MdmProviderNotDuplicateGoldenResourceR4Test extends BaseProviderR4Test {
@Autowired
IMdmLinkSvc myMdmLinkSvc;
private Patient myGoldenPatient;
private StringType myGoldenPatientId;
private Patient myTargetPatient;
private StringType myTargetPatientId;
@Override
@BeforeEach
public void before() {
super.before();
myGoldenPatient = createGoldenPatient();
myGoldenPatientId = new StringType(myGoldenPatient.getIdElement().getValue());
myTargetPatient = createGoldenPatient();
myTargetPatientId = new StringType(myTargetPatient.getIdElement().getValue());
}
@AfterEach
public void after() throws IOException {
myPartitionSettings.setPartitioningEnabled(false);
super.after();
}
@Test
public void testNotDuplicateGoldenResource() {
myMdmLinkSvc.updateLink(myGoldenPatient, myTargetPatient, MdmMatchOutcome.POSSIBLE_DUPLICATE, MdmLinkSourceEnum.AUTO, createContextForCreate("Patient"));
assertLinkCount(1);
myMdmProvider.notDuplicate(myGoldenPatientId, myTargetPatientId, myRequestDetails);
assertLinkCount(1);
List<MdmLink> links = myMdmLinkDaoSvc.findMdmLinksBySourceResource(myTargetPatient);
assertEquals(MdmLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
assertEquals(MdmMatchResultEnum.NO_MATCH, links.get(0).getMatchResult());
}
@Test
public void testNotDuplicateGoldenResourceNoLinkBetweenResources() {
try {
myMdmProvider.notDuplicate(myGoldenPatientId, myTargetPatientId, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), startsWith("HAPI-0745: No link exists between"));
}
}
@Test
public void testNotDuplicateGoldenResourceNotPossibleDuplicate() {
myMdmLinkSvc.updateLink(myGoldenPatient, myTargetPatient, MdmMatchOutcome.POSSIBLE_MATCH, MdmLinkSourceEnum.AUTO, createContextForCreate("Patient"));
assertLinkCount(1);
try {
myMdmProvider.notDuplicate(myGoldenPatientId, myTargetPatientId, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), endsWith("are not linked as POSSIBLE_DUPLICATE."));
}
}
@Test
public void testNotDuplicateGoldenResourceOnSamePartition() {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(1).setName(PARTITION_1));
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionId(1);
Patient goldenPatient = createPatientOnPartition(new Patient(), true, false, requestPartitionId);
StringType goldenPatientId = new StringType(goldenPatient.getIdElement().getValue());
Patient targetPatient = createPatientOnPartition(new Patient(), true, false, requestPartitionId);
StringType targetPatientId = new StringType(targetPatient.getIdElement().getValue());
myMdmLinkSvc.updateLink(goldenPatient, targetPatient, MdmMatchOutcome.POSSIBLE_DUPLICATE, MdmLinkSourceEnum.AUTO, createContextForCreate("Patient"));
assertLinkCount(1);
myMdmProvider.notDuplicate(goldenPatientId, targetPatientId, myRequestDetails);
assertLinkCount(1);
List<MdmLink> links = myMdmLinkDaoSvc.findMdmLinksBySourceResource(targetPatient);
assertEquals(MdmLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
assertEquals(MdmMatchResultEnum.NO_MATCH, links.get(0).getMatchResult());
}
@Test
public void testNotDuplicateGoldenResourceOnDifferentPartitions() {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(1).setName(PARTITION_1));
RequestPartitionId requestPartitionId1 = RequestPartitionId.fromPartitionId(1);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(2).setName(PARTITION_2));
RequestPartitionId requestPartitionId2 = RequestPartitionId.fromPartitionId(2);
Patient goldenPatient = createPatientOnPartition(new Patient(), true, false, requestPartitionId1);
StringType goldenPatientId = new StringType(goldenPatient.getIdElement().getValue());
Patient targetPatient = createPatientOnPartition(new Patient(), true, false, requestPartitionId2);
StringType targetPatientId = new StringType(targetPatient.getIdElement().getValue());
try {
myMdmProvider.notDuplicate(goldenPatientId, targetPatientId, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), startsWith("HAPI-0745: No link exists between"));
}
}
}

View File

@ -1,7 +1,9 @@
package ca.uhn.fhir.jpa.mdm.provider;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
@ -54,6 +56,52 @@ public class MdmProviderUpdateLinkR4Test extends BaseLinkR4Test {
assertEquals(MdmMatchResultEnum.MATCH, links.get(0).getMatchResult());
}
@Test
public void testUpdateLinkMatchOnSamePartition() {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(1).setName(PARTITION_1));
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionId(1);
Patient patient = createPatientAndUpdateLinksOnPartition(buildFrankPatient(), requestPartitionId);
StringType patientId = new StringType(patient.getIdElement().getValue());
Patient sourcePatient = getGoldenResourceFromTargetResource(patient);
StringType sourcePatientId = new StringType(sourcePatient.getIdElement().getValue());
MdmLink link = myMdmLinkDaoSvc.findMdmLinkBySource(patient).get();
link.setMatchResult(MdmMatchResultEnum.POSSIBLE_MATCH);
saveLink(link);
assertEquals(MdmLinkSourceEnum.AUTO, link.getLinkSource());
assertLinkCount(2);
myMdmProvider.updateLink(sourcePatientId, patientId, MATCH_RESULT, myRequestDetails);
assertLinkCount(2);
List<MdmLink> links = myMdmLinkDaoSvc.findMdmLinksBySourceResource(patient);
assertEquals(MdmLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
assertEquals(MdmMatchResultEnum.MATCH, links.get(0).getMatchResult());
assertNotNull(links.get(0).getPartitionId());
assertEquals(1, links.get(0).getPartitionId().getPartitionId());
}
@Test
public void testUpdateLinkMatchOnDifferentPartitions() {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(1).setName(PARTITION_1));
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(2).setName(PARTITION_2));
RequestPartitionId requestPartitionId1 = RequestPartitionId.fromPartitionId(1);
RequestPartitionId requestPartitionId2 = RequestPartitionId.fromPartitionId(2);
Patient patient = createPatientOnPartition(buildFrankPatient(), true, false, requestPartitionId1);
StringType patientId = new StringType(patient.getIdElement().getValue());
Patient sourcePatient = createPatientOnPartition(buildJanePatient(), true, false, requestPartitionId2);
StringType sourcePatientId = new StringType(sourcePatient.getIdElement().getValue());
assertLinkCount(1);
try {
myMdmProvider.updateLink(sourcePatientId, patientId, MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), endsWith("This operation is only available for resources on the same partition."));
}
}
@Test
public void testUpdateLinkTwiceFailsDueToWrongVersion() {
myMdmProvider.updateLink(mySourcePatientId, myPatientId, MATCH_RESULT, myRequestDetails);

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.mdm.api.IMdmSubmitSvc;
import ca.uhn.test.concurrency.PointcutLatch;
import org.apache.commons.lang3.time.DateUtils;
@ -56,7 +57,7 @@ class MdmBatchSvcImplIT extends BaseMdmR4Test {
assertLinkCount(0);
//SUT
afterMdmLatch.runWithExpectedCount(30, () -> myMdmSubmitSvc.submitAllSourceTypesToMdm(null));
afterMdmLatch.runWithExpectedCount(30, () -> myMdmSubmitSvc.submitAllSourceTypesToMdm(null, SystemRequestDetails.forAllPartitions()));
assertLinkCount(30);
}
@ -72,7 +73,7 @@ class MdmBatchSvcImplIT extends BaseMdmR4Test {
//SUT
myMdmSubmitSvc.setBufferSize(5);
afterMdmLatch.runWithExpectedCount(10, () -> myMdmSubmitSvc.submitSourceResourceTypeToMdm("Patient", null));
afterMdmLatch.runWithExpectedCount(10, () -> myMdmSubmitSvc.submitSourceResourceTypeToMdm("Patient", null, SystemRequestDetails.newSystemRequestAllPartitions()));
assertLinkCount(10);
}
@ -89,7 +90,7 @@ class MdmBatchSvcImplIT extends BaseMdmR4Test {
assertLinkCount(0);
//SUT
afterMdmLatch.runWithExpectedCount(10, () -> myMdmSubmitSvc.submitSourceResourceTypeToMdm("Medication", null));
afterMdmLatch.runWithExpectedCount(10, () -> myMdmSubmitSvc.submitSourceResourceTypeToMdm("Medication", null, SystemRequestDetails.newSystemRequestAllPartitions()));
assertLinkCount(10);
}
@ -104,7 +105,7 @@ class MdmBatchSvcImplIT extends BaseMdmR4Test {
assertLinkCount(0);
//SUT
afterMdmLatch.runWithExpectedCount(10, () -> myMdmSubmitSvc.submitAllSourceTypesToMdm(null));
afterMdmLatch.runWithExpectedCount(10, () -> myMdmSubmitSvc.submitAllSourceTypesToMdm(null, SystemRequestDetails.newSystemRequestAllPartitions()));
assertLinkCount(10);
}
@ -117,7 +118,7 @@ class MdmBatchSvcImplIT extends BaseMdmR4Test {
assertLinkCount(0);
//SUT
afterMdmLatch.runWithExpectedCount(1, () -> myMdmSubmitSvc.submitSourceResourceTypeToMdm("Patient", "Patient?name=gary"));
afterMdmLatch.runWithExpectedCount(1, () -> myMdmSubmitSvc.submitSourceResourceTypeToMdm("Patient", "Patient?name=gary", SystemRequestDetails.newSystemRequestAllPartitions()));
assertLinkCount(1);
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmCandidateSearchSvc;
import ca.uhn.fhir.jpa.mdm.svc.candidate.TooManyCandidatesException;
@ -38,7 +39,7 @@ public class MdmCandidateSearchSvcIT extends BaseMdmR4Test {
createActivePatient();
Patient newJane = buildJanePatient();
Collection<IAnyResource> result = myMdmCandidateSearchSvc.findCandidates("Patient", newJane);
Collection<IAnyResource> result = myMdmCandidateSearchSvc.findCandidates("Patient", newJane, RequestPartitionId.allPartitions());
assertEquals(1, result.size());
}
@ -53,7 +54,7 @@ public class MdmCandidateSearchSvcIT extends BaseMdmR4Test {
Patient newJane = buildJaneWithBirthday(today);
Collection<IAnyResource> result = myMdmCandidateSearchSvc.findCandidates("Patient", newJane);
Collection<IAnyResource> result = myMdmCandidateSearchSvc.findCandidates("Patient", newJane, RequestPartitionId.allPartitions());
assertEquals(1, result.size());
}
@ -71,7 +72,7 @@ public class MdmCandidateSearchSvcIT extends BaseMdmR4Test {
incomingPatient.setActive(true);
incomingPatient.setGeneralPractitioner(Collections.singletonList(new Reference(practitionerAndUpdateLinks.getId())));
Collection<IAnyResource> patient = myMdmCandidateSearchSvc.findCandidates("Patient", incomingPatient);
Collection<IAnyResource> patient = myMdmCandidateSearchSvc.findCandidates("Patient", incomingPatient, RequestPartitionId.allPartitions());
assertThat(patient, hasSize(1));
}
@ -82,13 +83,13 @@ public class MdmCandidateSearchSvcIT extends BaseMdmR4Test {
Patient newJane = buildJanePatient();
createActivePatient();
assertEquals(1, runInTransaction(()->myMdmCandidateSearchSvc.findCandidates("Patient", newJane).size()));
assertEquals(1, runInTransaction(()->myMdmCandidateSearchSvc.findCandidates("Patient", newJane, RequestPartitionId.allPartitions()).size()));
createActivePatient();
assertEquals(2, runInTransaction(()->myMdmCandidateSearchSvc.findCandidates("Patient", newJane).size()));
assertEquals(2, runInTransaction(()->myMdmCandidateSearchSvc.findCandidates("Patient", newJane, RequestPartitionId.allPartitions()).size()));
try {
createActivePatient();
myMdmCandidateSearchSvc.findCandidates("Patient", newJane);
myMdmCandidateSearchSvc.findCandidates("Patient", newJane, RequestPartitionId.allPartitions());
fail();
} catch (TooManyCandidatesException e) {
assertEquals("More than 3 candidate matches found for Patient?identifier=http%3A%2F%2Fa.tv%2F%7CID.JANE.123&active=true. Aborting mdm matching.", e.getMessage());

View File

@ -0,0 +1,158 @@
package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.jpa.mdm.provider.BaseLinkR4Test;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.mdm.api.IMdmControllerSvc;
import ca.uhn.fhir.mdm.api.MdmLinkJson;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.test.utilities.BatchJobHelper;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.DecimalType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.data.domain.Page;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import static ca.uhn.fhir.mdm.provider.MdmProviderDstu3Plus.DEFAULT_PAGE_SIZE;
import static ca.uhn.fhir.mdm.provider.MdmProviderDstu3Plus.MAX_PAGE_SIZE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
public class MdmControllerSvcImplTest extends BaseLinkR4Test {
@Autowired
IMdmControllerSvc myMdmControllerSvc;
@SpyBean
@Autowired
IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
@Autowired
private IInterceptorService myInterceptorService;
@Autowired
private BatchJobHelper myBatchJobHelper;
@BeforeEach
public void before() {
super.before();
myPartitionSettings.setPartitioningEnabled(true);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(1).setName(PARTITION_1));
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(2).setName(PARTITION_2));
myInterceptorService.registerInterceptor(new RequestTenantPartitionInterceptor());
}
@Test
public void testSurvivorshipIsCalledOnMatchingToTheSameGoldenResource() {
assertLinkCount(1);
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionId(1);
Patient patient = createPatientAndUpdateLinksOnPartition(buildFrankPatient(), requestPartitionId);
getGoldenResourceFromTargetResource(patient);
MdmLink link = myMdmLinkDaoSvc.findMdmLinkBySource(patient).get();
link.setMatchResult(MdmMatchResultEnum.POSSIBLE_MATCH);
saveLink(link);
assertEquals(MdmLinkSourceEnum.AUTO, link.getLinkSource());
assertLinkCount(2);
Page<MdmLinkJson> resultPage = myMdmControllerSvc.queryLinks(null, myPatientId.getIdElement().getValue(), null, null,
new MdmTransactionContext(MdmTransactionContext.OperationType.QUERY_LINKS),
new MdmPageRequest((Integer) null, null, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE),
new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.fromPartitionId(1)));
assertEquals(resultPage.getContent().size(), 1);
assertEquals(resultPage.getContent().get(0).getSourceId(), patient.getIdElement().getResourceType() + "/" + patient.getIdElement().getIdPart());
Mockito.verify(myRequestPartitionHelperSvc, Mockito.atLeastOnce()).validateHasPartitionPermissions(any(), eq("Patient"), argThat(new PartitionIdMatcher(requestPartitionId)));
}
@Test
public void testMdmDuplicateGoldenResource() {
assertLinkCount(1);
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionId(1);
Patient patient = createPatientAndUpdateLinksOnPartition(buildFrankPatient(), requestPartitionId);
getGoldenResourceFromTargetResource(patient);
MdmLink link = myMdmLinkDaoSvc.findMdmLinkBySource(patient).get();
link.setMatchResult(MdmMatchResultEnum.POSSIBLE_DUPLICATE);
saveLink(link);
assertEquals(MdmLinkSourceEnum.AUTO, link.getLinkSource());
assertLinkCount(2);
Page<MdmLinkJson> resultPage = myMdmControllerSvc.getDuplicateGoldenResources(null,
new MdmPageRequest((Integer) null, null, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE),
new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.fromPartitionId(1)));
assertEquals(resultPage.getContent().size(), 1);
assertEquals(resultPage.getContent().get(0).getSourceId(), patient.getIdElement().getResourceType() + "/" + patient.getIdElement().getIdPart());
Mockito.verify(myRequestPartitionHelperSvc, Mockito.atLeastOnce()).validateHasPartitionPermissions(any(), eq("Patient"), argThat(new PartitionIdMatcher(requestPartitionId)));
}
@Test
public void testMdmClearWithProvidedResources() {
assertLinkCount(1);
RequestPartitionId requestPartitionId1 = RequestPartitionId.fromPartitionId(1);
RequestPartitionId requestPartitionId2 = RequestPartitionId.fromPartitionId(2);
createPractitionerAndUpdateLinksOnPartition(buildJanePractitioner(), requestPartitionId1);
createPractitionerAndUpdateLinksOnPartition(buildJanePractitioner(), requestPartitionId2);
assertLinkCount(3);
List<String> urls = new ArrayList<>();
urls.add("Practitioner?");
IPrimitiveType<BigDecimal> batchSize = new DecimalType(new BigDecimal(100));
ServletRequestDetails details = new ServletRequestDetails();
details.setTenantId(PARTITION_2);
IBaseParameters clearJob = myMdmControllerSvc.submitMdmClearJob(urls, batchSize, details);
Long jobId = Long.valueOf(((DecimalType) ((Parameters) clearJob).getParameter("jobId")).getValueAsString());
JobExecution jobExecution = myBatchJobHelper.awaitJobExecution(jobId);
assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
assertLinkCount(2);
}
private class PartitionIdMatcher implements ArgumentMatcher<RequestPartitionId> {
private RequestPartitionId myRequestPartitionId;
PartitionIdMatcher(RequestPartitionId theRequestPartitionId) {
myRequestPartitionId = theRequestPartitionId;
}
@Override
public boolean matches(RequestPartitionId theRequestPartitionId) {
return myRequestPartitionId.getPartitionIds().equals(theRequestPartitionId.getPartitionIds());
}
}
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.dao.expunge.ExpungeEverythingService;
import ca.uhn.fhir.jpa.entity.MdmLink;
@ -8,6 +9,7 @@ import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hamcrest.Matchers;
import org.hl7.fhir.r4.model.IdType;
@ -182,4 +184,19 @@ public class MdmLinkSvcTest extends BaseMdmR4Test {
assertThat(actual, Matchers.containsInAnyOrder(expected.toArray()));
}
@Test
public void testMdmLinksHasPartitionIdForResourceOnNonDefaultPartition() {
Patient goldenPatient = createGoldenPatient(buildJanePatient());
Patient patient1 = createPatient(buildJanePatient());
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionId(1);
patient1.setUserData(Constants.RESOURCE_PARTITION_ID, requestPartitionId);
assertEquals(0, myMdmLinkDao.count());
myMdmLinkDaoSvc.createOrUpdateLinkEntity(goldenPatient, patient1, MdmMatchOutcome.NEW_GOLDEN_RESOURCE_MATCH, MdmLinkSourceEnum.MANUAL, createContextForCreate("Patient"));
List<MdmLink> targets = myMdmLinkDaoSvc.findMdmLinksByGoldenResource(goldenPatient);
assertFalse(targets.isEmpty());
assertEquals(1, targets.size());
assertEquals(requestPartitionId.getFirstPartitionIdOrNull(), targets.get(0).getPartitionId().getPartitionId());
}
}

View File

@ -1,19 +1,10 @@
package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.jpa.entity.MdmLink;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.mdm.api.IMdmLinkSvc;
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.TokenParam;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
@ -23,11 +14,6 @@ import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import javax.annotation.Nullable;
import static ca.uhn.fhir.mdm.api.MdmMatchResultEnum.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.times;
import static org.slf4j.LoggerFactory.getLogger;

View File

@ -1,16 +1,23 @@
package ca.uhn.fhir.jpa.mdm.svc;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.util.Optional;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class MdmResourceDaoSvcTest extends BaseMdmR4Test {
@ -18,6 +25,12 @@ public class MdmResourceDaoSvcTest extends BaseMdmR4Test {
@Autowired
MdmResourceDaoSvc myResourceDaoSvc;
@AfterEach
public void after() throws IOException {
myPartitionSettings.setPartitioningEnabled(new PartitionSettings().isPartitioningEnabled());
super.after();
}
@Test
public void testSearchPatientByEidExcludesNonGoldenPatients() {
Patient goodSourcePatient = addExternalEID(createGoldenPatient(), TEST_EID);
@ -35,7 +48,7 @@ public class MdmResourceDaoSvcTest extends BaseMdmR4Test {
}
@Test
public void testSearcGoldenResourceByEidExcludesNonMdmManaged() {
public void testSearchGoldenResourceByEidExcludesNonMdmManaged() {
Patient goodSourcePatient = addExternalEID(createGoldenPatient(), TEST_EID);
myPatientDao.update(goodSourcePatient);
@ -46,4 +59,37 @@ public class MdmResourceDaoSvcTest extends BaseMdmR4Test {
assertTrue(foundSourcePatient.isPresent());
assertThat(foundSourcePatient.get().getIdElement().toUnqualifiedVersionless().getValue(), is(goodSourcePatient.getIdElement().toUnqualifiedVersionless().getValue()));
}
@Test
public void testSearchGoldenResourceOnSamePartition() {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(1).setName(PARTITION_1));
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionId(1);
Patient patientOnPartition = createPatientOnPartition(new Patient(), true, false, requestPartitionId);
Patient goodSourcePatient = addExternalEID(patientOnPartition, TEST_EID);
SystemRequestDetails systemRequestDetails = new SystemRequestDetails();
systemRequestDetails.setRequestPartitionId(requestPartitionId);
myPatientDao.update(goodSourcePatient, systemRequestDetails);
Optional<IAnyResource> foundSourcePatient = myResourceDaoSvc.searchGoldenResourceByEID(TEST_EID, "Patient", requestPartitionId);
assertTrue(foundSourcePatient.isPresent());
assertThat(foundSourcePatient.get().getIdElement().toUnqualifiedVersionless().getValue(), is(goodSourcePatient.getIdElement().toUnqualifiedVersionless().getValue()));
}
@Test
public void testSearchGoldenResourceOnDifferentPartitions() {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(1).setName(PARTITION_1));
RequestPartitionId requestPartitionId1 = RequestPartitionId.fromPartitionId(1);
myPartitionLookupSvc.createPartition(new PartitionEntity().setId(2).setName(PARTITION_2));
RequestPartitionId requestPartitionId2 = RequestPartitionId.fromPartitionId(2);
Patient patientOnPartition = createPatientOnPartition(new Patient(), true, false, requestPartitionId1);
Patient goodSourcePatient = addExternalEID(patientOnPartition, TEST_EID);
SystemRequestDetails systemRequestDetails = new SystemRequestDetails();
systemRequestDetails.setRequestPartitionId(requestPartitionId1);
myPatientDao.update(goodSourcePatient, systemRequestDetails);
Optional<IAnyResource> foundSourcePatient = myResourceDaoSvc.searchGoldenResourceByEID(TEST_EID, "Patient", requestPartitionId2);
assertFalse(foundSourcePatient.isPresent());
}
}

View File

@ -20,6 +20,8 @@ import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -54,7 +56,7 @@ class CandidateSearcherTest {
SimpleBundleProvider bundleProvider = new SimpleBundleProvider();
bundleProvider.setSize(candidateSearchLimit + offset);
when(dao.search(map)).thenReturn(bundleProvider);
when(dao.search(eq(map), any())).thenReturn(bundleProvider);
Optional<IBundleProvider> result = myCandidateSearcher.search(resourceType, criteria);

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.subscription.match.config;
* #L%
*/
import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory;
import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelRegistry;
import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryChannelNamer;
import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryHandlerFactory;
@ -38,6 +39,7 @@ import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionRegiste
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionLoader;
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry;
import ca.uhn.fhir.jpa.subscription.model.config.SubscriptionModelConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
@ -103,8 +105,8 @@ public class SubscriptionProcessorConfig {
@Bean
@Scope("prototype")
public SubscriptionDeliveringMessageSubscriber subscriptionDeliveringMessageSubscriber() {
return new SubscriptionDeliveringMessageSubscriber();
public SubscriptionDeliveringMessageSubscriber subscriptionDeliveringMessageSubscriber(IChannelFactory theChannelFactory) {
return new SubscriptionDeliveringMessageSubscriber(theChannelFactory);
}
@Bean

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
import ca.uhn.fhir.rest.api.EncodingEnum;
import com.google.common.annotations.VisibleForTesting;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -44,16 +45,16 @@ import java.net.URISyntaxException;
@Scope("prototype")
public class SubscriptionDeliveringMessageSubscriber extends BaseSubscriptionDeliverySubscriber {
private static Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringMessageSubscriber.class);
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringMessageSubscriber.class);
@Autowired
private IChannelFactory myChannelFactory;
private final IChannelFactory myChannelFactory;
/**
* Constructor
*/
public SubscriptionDeliveringMessageSubscriber() {
public SubscriptionDeliveringMessageSubscriber(IChannelFactory theChannelFactory) {
super();
myChannelFactory = theChannelFactory;
}
protected void deliverPayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, IChannelProducer theChannelProducer) {
@ -67,6 +68,7 @@ public class SubscriptionDeliveringMessageSubscriber extends BaseSubscriptionDel
ResourceModifiedMessage payload = new ResourceModifiedMessage(myFhirContext, thePayloadResource, theMsg.getOperationType());
payload.setMessageKey(theMsg.getMessageKeyOrNull());
payload.setTransactionId(theMsg.getTransactionId());
payload.setPartitionId(theMsg.getRequestPartitionId());
ResourceModifiedJsonMessage message = new ResourceModifiedJsonMessage(payload);
theChannelProducer.send(message);
ourLog.debug("Delivering {} message payload {} for {}", theMsg.getOperationType(), theMsg.getPayloadId(), theSubscription.getIdElement(myFhirContext).toUnqualifiedVersionless().getValue());

View File

@ -118,13 +118,13 @@ public class SubscriptionActivatingSubscriber extends BaseSubscriberForSubscript
@SuppressWarnings("unchecked")
private boolean activateSubscription(final IBaseResource theSubscription) {
IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao();
SystemRequestDetails srd = SystemRequestDetails.forAllPartition();
SystemRequestDetails srd = SystemRequestDetails.forAllPartitions();
IBaseResource subscription = null;
try {
// read can throw ResourceGoneException
// if this happens, we will treat this as a failure to activate
subscription = subscriptionDao.read(theSubscription.getIdElement(), SystemRequestDetails.forAllPartition());
subscription = subscriptionDao.read(theSubscription.getIdElement(), SystemRequestDetails.forAllPartitions());
subscription.setId(subscription.getIdElement().toVersionless());
ourLog.info("Activating subscription {} from status {} to {}", subscription.getIdElement().toUnqualified().getValue(), SubscriptionConstants.REQUESTED_STATUS, SubscriptionConstants.ACTIVE_STATUS);

View File

@ -90,7 +90,7 @@ public class SubscriptionLoader implements IResourceChangeListener {
@PostConstruct
public void registerListener() {
mySearchParameterMap = getSearchParameterMap();
mySystemRequestDetails = SystemRequestDetails.forAllPartition();
mySystemRequestDetails = SystemRequestDetails.forAllPartitions();
IResourceChangeListenerCache subscriptionCache = myResourceChangeListenerRegistry.registerResourceResourceChangeListener("Subscription", mySearchParameterMap, this, REFRESH_INTERVAL);
subscriptionCache.forceRefresh();
@ -252,7 +252,7 @@ public class SubscriptionLoader implements IResourceChangeListener {
return;
}
IFhirResourceDao<?> subscriptionDao = getSubscriptionDao();
SystemRequestDetails systemRequestDetails = SystemRequestDetails.forAllPartition();
SystemRequestDetails systemRequestDetails = SystemRequestDetails.forAllPartitions();
List<IBaseResource> resourceList = theResourceIds.stream().map(n -> subscriptionDao.read(n, systemRequestDetails)).collect(Collectors.toList());
updateSubscriptionRegistry(resourceList);
}

View File

@ -116,7 +116,7 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
if (!subscriptionId.hasResourceType()) {
subscriptionId = subscriptionId.withResourceType(ResourceTypeEnum.SUBSCRIPTION.getCode());
}
subscriptionDao.read(subscriptionId, SystemRequestDetails.forAllPartition());
subscriptionDao.read(subscriptionId, SystemRequestDetails.forAllPartitions());
}
List<IPrimitiveType<String>> resourceIds = ObjectUtils.defaultIfNull(theResourceIds, Collections.emptyList());
@ -300,7 +300,7 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
private Future<Void> submitResource(String theSubscriptionId, String theResourceIdToTrigger) {
org.hl7.fhir.r4.model.IdType resourceId = new org.hl7.fhir.r4.model.IdType(theResourceIdToTrigger);
IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceId.getResourceType());
IBaseResource resourceToTrigger = dao.read(resourceId, SystemRequestDetails.forAllPartition());
IBaseResource resourceToTrigger = dao.read(resourceId, SystemRequestDetails.forAllPartitions());
return submitResource(theSubscriptionId, resourceToTrigger);
}

View File

@ -5,11 +5,15 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory;
import ca.uhn.fhir.jpa.subscription.channel.api.IChannelProducer;
import ca.uhn.fhir.jpa.subscription.match.deliver.message.SubscriptionDeliveringMessageSubscriber;
import ca.uhn.fhir.jpa.subscription.match.deliver.resthook.SubscriptionDeliveringRestHookSubscriber;
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry;
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryJsonMessage;
import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
@ -23,6 +27,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Logger;
@ -31,7 +36,9 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.GenericMessage;
import java.net.URISyntaxException;
import java.time.LocalDate;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
@ -49,12 +56,18 @@ public class BaseSubscriptionDeliverySubscriberTest {
private static final Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionDeliverySubscriberTest.class);
private SubscriptionDeliveringRestHookSubscriber mySubscriber;
private SubscriptionDeliveringMessageSubscriber myMessageSubscriber;
private final FhirContext myCtx = FhirContext.forR4();
@Mock
private IInterceptorBroadcaster myInterceptorBroadcaster;
@Mock
protected SubscriptionRegistry mySubscriptionRegistry;
@Mock
private IChannelFactory myChannelFactory;
@Mock
private IChannelProducer myChannelProducer;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private IRestfulClientFactory myRestfulClientFactory;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
@ -67,6 +80,11 @@ public class BaseSubscriptionDeliverySubscriberTest {
mySubscriber.setInterceptorBroadcasterForUnitTest(myInterceptorBroadcaster);
mySubscriber.setSubscriptionRegistryForUnitTest(mySubscriptionRegistry);
myMessageSubscriber = new SubscriptionDeliveringMessageSubscriber(myChannelFactory);
myMessageSubscriber.setFhirContextForUnitTest(myCtx);
myMessageSubscriber.setInterceptorBroadcasterForUnitTest(myInterceptorBroadcaster);
myMessageSubscriber.setSubscriptionRegistryForUnitTest(mySubscriptionRegistry);
myCtx.setRestfulClientFactory(myRestfulClientFactory);
when(myRestfulClientFactory.newGenericClient(any())).thenReturn(myGenericClient);
}
@ -194,7 +212,6 @@ public class BaseSubscriptionDeliverySubscriberTest {
ourLog.info(jsonString);
// Assert that the partitionID is being serialized in JSON
assertThat(jsonString, containsString("\"partitionDate\":[2020,1,1]"));
assertThat(jsonString, containsString("\"partitionIds\":[123]"));
@ -223,6 +240,30 @@ public class BaseSubscriptionDeliverySubscriberTest {
assertThat(jsonString, containsString("\"partitionIds\":[null]"));
}
@Test
public void testDeliveryMessageWithPartition() throws URISyntaxException {
RequestPartitionId thePartitionId = RequestPartitionId.fromPartitionId(123, LocalDate.of(2020, 1, 1));
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY), any())).thenReturn(true);
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.SUBSCRIPTION_AFTER_MESSAGE_DELIVERY), any())).thenReturn(false);
when(myChannelFactory.getOrCreateProducer(any(), any(), any())).thenReturn(myChannelProducer);
CanonicalSubscription subscription = generateSubscription();
Patient patient = generatePatient();
ResourceDeliveryMessage payload = new ResourceDeliveryMessage();
payload.setSubscription(subscription);
payload.setPayload(myCtx, patient, EncodingEnum.JSON);
payload.setOperationType(ResourceModifiedMessage.OperationTypeEnum.CREATE);
payload.setPartitionId(thePartitionId);
myMessageSubscriber.handleMessage(payload);
verify(myChannelFactory).getOrCreateProducer(any(), any(), any());
ArgumentCaptor<ResourceModifiedJsonMessage> captor = ArgumentCaptor.forClass(ResourceModifiedJsonMessage.class);
verify(myChannelProducer).send(captor.capture());
final List<ResourceModifiedJsonMessage> params = captor.getAllValues();
assertEquals(thePartitionId, params.get(0).getPayload().getPartitionId());
}
@Test
public void testSerializeLegacyDeliveryMessage() throws JsonProcessingException {
String legacyDeliveryMessageJson = "{\"headers\":{\"retryCount\":0,\"customHeaders\":{}},\"payload\":{\"operationType\":\"CREATE\",\"canonicalSubscription\":{\"id\":\"Subscription/123\",\"endpointUrl\":\"http://example.com/fhir\",\"payload\":\"application/fhir+json\"},\"payload\":\"{\\\"resourceType\\\":\\\"Patient\\\",\\\"active\\\":true}\"}}";
@ -250,4 +291,5 @@ public class BaseSubscriptionDeliverySubscriberTest {
subscription.setPayloadString("application/fhir+json");
return subscription;
}
}

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.mdm.api;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseParameters;
@ -34,10 +35,17 @@ import java.util.List;
public interface IMdmControllerSvc {
@Deprecated
Page<MdmLinkJson> queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest);
Page<MdmLinkJson> queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, RequestDetails theRequestDetails);
Page<MdmLinkJson> queryLinksFromPartitionList(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, List<Integer> thePartitionIds);
Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest);
Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, RequestDetails theRequestDetails);
void notDuplicateGoldenResource(String theGoldenResourceId, String theTargetGoldenResourceId, MdmTransactionContext theMdmTransactionContext);
IAnyResource mergeGoldenResources(String theFromGoldenResourceId, String theToGoldenResourceId, IAnyResource theManuallyMergedGoldenResource, MdmTransactionContext theMdmTransactionContext);

View File

@ -25,10 +25,14 @@ import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.data.domain.Page;
import java.util.List;
/**
* This service supports the MDM operation providers for those services that return multiple MDM links.
*/
public interface IMdmLinkQuerySvc {
Page<MdmLinkJson> queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest);
Page<MdmLinkJson> queryLinks(IIdType theGoldenResourceId, IIdType theSourceResourceId, MdmMatchResultEnum theMatchResult, MdmLinkSourceEnum theLinkSource, MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest, List<Integer> thePartitionId);
Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest);
Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmContext, MdmPageRequest thePageRequest, List<Integer> thePartitionId);
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.mdm.api;
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import org.hl7.fhir.instance.model.api.IAnyResource;
import javax.annotation.Nonnull;
@ -36,5 +37,5 @@ public interface IMdmMatchFinderSvc {
* @return a List of {@link MatchedTarget} representing POSSIBLE_MATCH and MATCH outcomes.
*/
@Nonnull
List<MatchedTarget> getMatchedTargets(String theResourceType, IAnyResource theResource);
List<MatchedTarget> getMatchedTargets(String theResourceType, IAnyResource theResource, RequestPartitionId theRequestPartitionId);
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.mdm.api;
* #L%
*/
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nullable;
@ -37,7 +38,7 @@ public interface IMdmSubmitSvc {
*
* @return
*/
long submitAllSourceTypesToMdm(@Nullable String theCriteria);
long submitAllSourceTypesToMdm(@Nullable String theCriteria, RequestDetails theRequestDetails);
/**
* Given a type and a search criteria, submit all found resources for MDM processing.
@ -46,7 +47,7 @@ public interface IMdmSubmitSvc {
* @param theCriteria The FHIR search critieria for filtering the resources to be submitted for MDM processing..
* @return the number of resources submitted for MDM processing.
*/
long submitSourceResourceTypeToMdm(String theSourceResourceType, String theCriteria);
long submitSourceResourceTypeToMdm(String theSourceResourceType, String theCriteria, RequestDetails theRequestDetails);
/**
* Convenience method that calls {@link #submitSourceResourceTypeToMdm(String, String)} with the type pre-populated.
@ -54,7 +55,7 @@ public interface IMdmSubmitSvc {
* @param theCriteria The FHIR search critieria for filtering the resources to be submitted for MDM processing.
* @return the number of resources submitted for MDM processing.
*/
long submitPractitionerTypeToMdm(String theCriteria);
long submitPractitionerTypeToMdm(String theCriteria, RequestDetails theRequestDetails);
/**
* Convenience method that calls {@link #submitSourceResourceTypeToMdm(String, String)} with the type pre-populated.
@ -62,7 +63,7 @@ public interface IMdmSubmitSvc {
* @param theCriteria The FHIR search critieria for filtering the resources to be submitted for MDM processing.
* @return the number of resources submitted for MDM processing.
*/
long submitPatientTypeToMdm(String theCriteria);
long submitPatientTypeToMdm(String theCriteria, RequestDetails theRequestDetails);
/**
* Given an ID and a source resource type valid for MDM, manually submit the given ID for MDM processing.
@ -70,7 +71,7 @@ public interface IMdmSubmitSvc {
* @param theId the ID of the resource to process for MDM.
* @return the constant `1`, as if this function returns successfully, it will have processed one resource for MDM.
*/
long submitSourceResourceToMdm(IIdType theId);
long submitSourceResourceToMdm(IIdType theId, RequestDetails theRequestDetails);
/**
* This setter exists to allow imported modules to override settings.

View File

@ -118,7 +118,7 @@ public abstract class BaseMdmProvider {
protected IBaseParameters parametersFromMdmLinks(Page<MdmLinkJson> theMdmLinkStream, boolean includeResultAndSource, ServletRequestDetails theServletRequestDetails, MdmPageRequest thePageRequest) {
IBaseParameters retval = ParametersUtil.newInstance(myFhirContext);
addPagingParameters(retval, theMdmLinkStream, theServletRequestDetails, thePageRequest);
theMdmLinkStream.forEach(mdmLink -> {
theMdmLinkStream.getContent().forEach(mdmLink -> {
IBase resultPart = ParametersUtil.addParameterToParameters(myFhirContext, retval, "link");
ParametersUtil.addPartString(myFhirContext, resultPart, "goldenResourceId", mdmLink.getGoldenResourceId());
ParametersUtil.addPartString(myFhirContext, resultPart, "sourceResourceId", mdmLink.getSourceId());

View File

@ -22,6 +22,8 @@ package ca.uhn.fhir.mdm.provider;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.mdm.api.IMdmMatchFinderSvc;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.api.MatchedTarget;
@ -29,6 +31,7 @@ import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.util.MdmResourceUtil;
import ca.uhn.fhir.mdm.util.MessageHelper;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
@ -60,18 +63,21 @@ public class MdmControllerHelper {
private final IMdmSettings myMdmSettings;
private final MessageHelper myMessageHelper;
private final IMdmMatchFinderSvc myMdmMatchFinderSvc;
private final IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
@Autowired
public MdmControllerHelper(FhirContext theFhirContext,
IResourceLoader theResourceLoader,
IMdmMatchFinderSvc theMdmMatchFinderSvc,
IMdmSettings theMdmSettings,
MessageHelper theMessageHelper) {
MessageHelper theMessageHelper,
IRequestPartitionHelperSvc theRequestPartitionHelperSvc) {
myFhirContext = theFhirContext;
myResourceLoader = theResourceLoader;
myMdmSettings = theMdmSettings;
myMdmMatchFinderSvc = theMdmMatchFinderSvc;
myMessageHelper = theMessageHelper;
myRequestPartitionHelperSvc = theRequestPartitionHelperSvc;
}
public void validateSameVersion(IAnyResource theResource, String theResourceId) {
@ -130,8 +136,9 @@ public class MdmControllerHelper {
/**
* Helper method which will return a bundle of all Matches and Possible Matches.
*/
public IBaseBundle getMatchesAndPossibleMatchesForResource(IAnyResource theResource, String theResourceType) {
List<MatchedTarget> matches = myMdmMatchFinderSvc.getMatchedTargets(theResourceType, theResource);
public IBaseBundle getMatchesAndPossibleMatchesForResource(IAnyResource theResource, String theResourceType, RequestDetails theRequestDetails) {
RequestPartitionId requestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, theResourceType, null);
List<MatchedTarget> matches = myMdmMatchFinderSvc.getMatchedTargets(theResourceType, theResource, requestPartitionId);
matches.sort(Comparator.comparing((MatchedTarget m) -> m.getMatchResult().getNormalizedScore()).reversed());
BundleBuilder builder = new BundleBuilder(myFhirContext);

View File

@ -87,21 +87,23 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider {
}
@Operation(name = ProviderConstants.EMPI_MATCH, typeName = "Patient")
public IBaseBundle match(@OperationParam(name = ProviderConstants.MDM_MATCH_RESOURCE, min = 1, max = 1, typeName = "Patient") IAnyResource thePatient) {
public IBaseBundle match(@OperationParam(name = ProviderConstants.MDM_MATCH_RESOURCE, min = 1, max = 1, typeName = "Patient") IAnyResource thePatient,
RequestDetails theRequestDetails) {
if (thePatient == null) {
throw new InvalidRequestException(Msg.code(1498) + "resource may not be null");
}
return myMdmControllerHelper.getMatchesAndPossibleMatchesForResource(thePatient, "Patient");
return myMdmControllerHelper.getMatchesAndPossibleMatchesForResource(thePatient, "Patient", theRequestDetails);
}
@Operation(name = ProviderConstants.MDM_MATCH)
public IBaseBundle serverMatch(@OperationParam(name = ProviderConstants.MDM_MATCH_RESOURCE, min = 1, max = 1) IAnyResource theResource,
@OperationParam(name = ProviderConstants.MDM_RESOURCE_TYPE, min = 1, max = 1, typeName = "string") IPrimitiveType<String> theResourceType
@OperationParam(name = ProviderConstants.MDM_RESOURCE_TYPE, min = 1, max = 1, typeName = "string") IPrimitiveType<String> theResourceType,
RequestDetails theRequestDetails
) {
if (theResource == null) {
throw new InvalidRequestException(Msg.code(1499) + "resource may not be null");
}
return myMdmControllerHelper.getMatchesAndPossibleMatchesForResource(theResource, theResourceType.getValueAsString());
return myMdmControllerHelper.getMatchesAndPossibleMatchesForResource(theResource, theResourceType.getValueAsString(), theRequestDetails);
}
@Operation(name = ProviderConstants.MDM_MERGE_GOLDEN_RESOURCES)
@ -184,13 +186,13 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider {
@Description(formalDefinition = "Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
@OperationParam(name = Constants.PARAM_COUNT, min = 0, max = 1, typeName = "integer")
IPrimitiveType<Integer> theCount,
ServletRequestDetails theRequestDetails) {
MdmPageRequest mdmPageRequest = new MdmPageRequest(theOffset, theCount, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
Page<MdmLinkJson> mdmLinkJson = myMdmControllerSvc.queryLinks(extractStringOrNull(theGoldenResourceId),
extractStringOrNull(theResourceId), extractStringOrNull(theMatchResult), extractStringOrNull(theLinkSource),
createMdmContext(theRequestDetails, MdmTransactionContext.OperationType.QUERY_LINKS,
getResourceType(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId)), mdmPageRequest);
getResourceType(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId)),
mdmPageRequest, theRequestDetails);
return parametersFromMdmLinks(mdmLinkJson, true, theRequestDetails, mdmPageRequest);
}
@ -207,7 +209,7 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider {
MdmPageRequest mdmPageRequest = new MdmPageRequest(theOffset, theCount, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
Page<MdmLinkJson> possibleDuplicates = myMdmControllerSvc.getDuplicateGoldenResources(createMdmContext(theRequestDetails, MdmTransactionContext.OperationType.DUPLICATE_GOLDEN_RESOURCES, null), mdmPageRequest);
Page<MdmLinkJson> possibleDuplicates = myMdmControllerSvc.getDuplicateGoldenResources(createMdmContext(theRequestDetails, MdmTransactionContext.OperationType.DUPLICATE_GOLDEN_RESOURCES, null), mdmPageRequest, theRequestDetails);
return parametersFromMdmLinks(possibleDuplicates, false, theRequestDetails, mdmPageRequest);
}
@ -239,9 +241,9 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider {
String resourceType = convertStringTypeToString(theResourceType);
long submittedCount;
if (resourceType != null) {
submittedCount = myMdmSubmitSvc.submitSourceResourceTypeToMdm(resourceType, criteria);
submittedCount = myMdmSubmitSvc.submitSourceResourceTypeToMdm(resourceType, criteria, theRequestDetails);
} else {
submittedCount = myMdmSubmitSvc.submitAllSourceTypesToMdm(criteria);
submittedCount = myMdmSubmitSvc.submitAllSourceTypesToMdm(criteria, theRequestDetails);
}
return buildMdmOutParametersWithCount(submittedCount);
}
@ -257,7 +259,7 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider {
public IBaseParameters mdmBatchPatientInstance(
@IdParam IIdType theIdParam,
RequestDetails theRequest) {
long submittedCount = myMdmSubmitSvc.submitSourceResourceToMdm(theIdParam);
long submittedCount = myMdmSubmitSvc.submitSourceResourceToMdm(theIdParam, theRequest);
return buildMdmOutParametersWithCount(submittedCount);
}
@ -268,7 +270,7 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider {
@OperationParam(name = ProviderConstants.MDM_BATCH_RUN_CRITERIA, typeName = "string") IPrimitiveType<String> theCriteria,
RequestDetails theRequest) {
String criteria = convertStringTypeToString(theCriteria);
long submittedCount = myMdmSubmitSvc.submitPatientTypeToMdm(criteria);
long submittedCount = myMdmSubmitSvc.submitPatientTypeToMdm(criteria, theRequest);
return buildMdmOutParametersWithCount(submittedCount);
}
@ -278,7 +280,7 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider {
public IBaseParameters mdmBatchPractitionerInstance(
@IdParam IIdType theIdParam,
RequestDetails theRequest) {
long submittedCount = myMdmSubmitSvc.submitSourceResourceToMdm(theIdParam);
long submittedCount = myMdmSubmitSvc.submitSourceResourceToMdm(theIdParam, theRequest);
return buildMdmOutParametersWithCount(submittedCount);
}
@ -289,7 +291,7 @@ public class MdmProviderDstu3Plus extends BaseMdmProvider {
@OperationParam(name = ProviderConstants.MDM_BATCH_RUN_CRITERIA, typeName = "string") IPrimitiveType<String> theCriteria,
RequestDetails theRequest) {
String criteria = convertStringTypeToString(theCriteria);
long submittedCount = myMdmSubmitSvc.submitPractitionerTypeToMdm(criteria);
long submittedCount = myMdmSubmitSvc.submitPractitionerTypeToMdm(criteria, theRequest);
return buildMdmOutParametersWithCount(submittedCount);
}

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.mdm.model.CanonicalEID;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.FhirTerser;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
@ -98,6 +99,9 @@ public class GoldenResourceHelper {
MdmResourceUtil.setMdmManaged(newGoldenResource);
MdmResourceUtil.setGoldenResource(newGoldenResource);
// add the partition id to the new resource
newGoldenResource.setUserData(Constants.RESOURCE_PARTITION_ID, theIncomingResource.getUserData(Constants.RESOURCE_PARTITION_ID));
return (T) newGoldenResource;
}

View File

@ -21,9 +21,9 @@ package ca.uhn.fhir.mdm.util;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.api.MdmConstants;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.springframework.beans.factory.annotation.Autowired;
@ -111,4 +111,13 @@ public class MessageHelper {
public String getMessageForFailedGoldenResourceLoad(String theParamName, String theGoldenResourceId) {
return theGoldenResourceId + " used as parameter [" + theParamName + "] could not be loaded as a golden resource, as it appears to be lacking the golden resource meta tags.";
}
public String getMessageForMismatchPartition(IAnyResource theGoldenRecord, IAnyResource theSourceResource) {
return getMessageForMismatchPartition(theGoldenRecord.getIdElement().toVersionless().toString(),
theSourceResource.getIdElement().toVersionless().toString());
}
public String getMessageForMismatchPartition(String theGoldenRecord, String theSourceResource) {
return theGoldenRecord + " and " + theSourceResource + " are stored in different partitions. This operation is only available for resources on the same partition.";
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -88,6 +88,7 @@ public class ProviderConstants {
public static final String MDM_QUERY_LINKS = "$mdm-query-links";
public static final String MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID = "goldenResourceId";
public static final String MDM_QUERY_LINKS_RESOURCE_ID = "resourceId";
public static final String MDM_QUERY_PARTITION_IDS = "partitionIds";
public static final String MDM_QUERY_LINKS_MATCH_RESULT = "matchResult";
public static final String MDM_QUERY_LINKS_LINK_SOURCE = "linkSource";

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.0.0-PRE9-SNAPSHOT</version>
<version>6.0.0-PRE10-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -35,7 +35,7 @@ import java.util.Set;
public interface IRequestPartitionHelperSvc {
@Nonnull
RequestPartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType, @Nonnull ReadPartitionIdRequestDetails theDetails);
RequestPartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, String theResourceType, ReadPartitionIdRequestDetails theDetails);
@Nonnull
default RequestPartitionId determineReadPartitionForRequestForRead(RequestDetails theRequest, String theResourceType, IIdType theId) {
@ -55,6 +55,9 @@ public interface IRequestPartitionHelperSvc {
return determineReadPartitionForRequest(theRequest, theResourceType, details);
}
@Nonnull
default void validateHasPartitionPermissions(RequestDetails theRequest, String theResourceType, RequestPartitionId theRequestPartitionId){}
@Nonnull
RequestPartitionId determineCreatePartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource, @Nonnull String theResourceType);

Some files were not shown because too many files have changed in this diff Show More