Reverse changes from 5493. Ensure MDM expansion is limited by queried partition. Support $everything with MDM expansion and POST (#5585)
* Reverse changes from 5493. Add a stub changelist. * Add various changes including stub changelist, beginnings of integration test, pass request details to interceptor, obtain partition ID, and stub code for each partition-aware query. * Spotless. Implement methods and SQLs for both golden and source IDs. Not yet tested. Add default interface methods. * Fix HQLs that were referring to incorrect columns. Rename parameter. Formatting. * Fix logic to use single queries by source and golden resource. Filter by partition IDs in Java instead of in SQL. Write a new unit test for MdmLinkExpandSvc. * Spotless. * Ensure $everything with mdm expansion works on POST as well as GET. Some cleanup. * More cleanup. * Null safety for mdmExpand. * Spotless. * Small changes. * Enhance changelog. * Delete commented out code. * First round of code review changes. * More code review changes.
This commit is contained in:
parent
d6128dece0
commit
763894c28f
|
@ -105,6 +105,10 @@ public class RequestPartitionId implements IModelJson {
|
||||||
return myAllPartitions;
|
return myAllPartitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPartitionCovered(Integer thePartitionId) {
|
||||||
|
return isAllPartitions() || getPartitionIds().contains(thePartitionId);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public LocalDate getPartitionDate() {
|
public LocalDate getPartitionDate() {
|
||||||
return myPartitionDate;
|
return myPartitionDate;
|
||||||
|
|
|
@ -219,6 +219,7 @@ public class Constants {
|
||||||
public static final String PARAM_TAGS = "_tags";
|
public static final String PARAM_TAGS = "_tags";
|
||||||
public static final String PARAM_TEXT = "_text";
|
public static final String PARAM_TEXT = "_text";
|
||||||
public static final String PARAM_VALIDATE = "_validate";
|
public static final String PARAM_VALIDATE = "_validate";
|
||||||
|
public static final String PARAM_MDM = "_mdm";
|
||||||
|
|
||||||
public static final String PARAMQUALIFIER_MISSING = ":missing";
|
public static final String PARAMQUALIFIER_MISSING = ":missing";
|
||||||
public static final String PARAMQUALIFIER_MISSING_FALSE = "false";
|
public static final String PARAMQUALIFIER_MISSING_FALSE = "false";
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
type: fix
|
||||||
|
title: "$everything queries with MDM expansion were bypassing the partition boundary by return resources outside the partition.
|
||||||
|
Also, POST (as opposed to GET) $everything queries with MDM expansion.
|
||||||
|
The first issue was fixed by reversing the previous changes in [5493](https://github.com/hapifhir/hapi-fhir/issues/5493)
|
||||||
|
and by filtering source and golden resources by partition ID.
|
||||||
|
The second issue was fixed by correctly capturing the MDM expansion flag in POST $everything queries."
|
|
@ -46,7 +46,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
public class JpaResourceDaoPatient<T extends IBaseResource> extends BaseHapiFhirResourceDao<T>
|
public class JpaResourceDaoPatient<T extends IBaseResource> extends BaseHapiFhirResourceDao<T>
|
||||||
|
@ -67,6 +66,7 @@ public class JpaResourceDaoPatient<T extends IBaseResource> extends BaseHapiFhir
|
||||||
StringAndListParam theNarrative,
|
StringAndListParam theNarrative,
|
||||||
StringAndListParam theFilter,
|
StringAndListParam theFilter,
|
||||||
StringAndListParam theTypes,
|
StringAndListParam theTypes,
|
||||||
|
boolean theMdmExpand,
|
||||||
RequestDetails theRequest) {
|
RequestDetails theRequest) {
|
||||||
SearchParameterMap paramMap = new SearchParameterMap();
|
SearchParameterMap paramMap = new SearchParameterMap();
|
||||||
if (theCount != null) {
|
if (theCount != null) {
|
||||||
|
@ -95,11 +95,8 @@ public class JpaResourceDaoPatient<T extends IBaseResource> extends BaseHapiFhir
|
||||||
paramMap.setSort(theSort);
|
paramMap.setSort(theSort);
|
||||||
paramMap.setLastUpdated(theLastUpdated);
|
paramMap.setLastUpdated(theLastUpdated);
|
||||||
if (theIds != null) {
|
if (theIds != null) {
|
||||||
if (theRequest.getParameters().containsKey("_mdm")) {
|
if (theMdmExpand) {
|
||||||
String[] paramVal = theRequest.getParameters().get("_mdm");
|
theIds.getValuesAsQueryTokens().forEach(param -> param.setMdmExpand(true));
|
||||||
if (Arrays.asList(paramVal).contains("true")) {
|
|
||||||
theIds.getValuesAsQueryTokens().forEach(param -> param.setMdmExpand(true));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
paramMap.add("_id", theIds);
|
paramMap.add("_id", theIds);
|
||||||
}
|
}
|
||||||
|
@ -161,6 +158,7 @@ public class JpaResourceDaoPatient<T extends IBaseResource> extends BaseHapiFhir
|
||||||
theQueryParams.getNarrative(),
|
theQueryParams.getNarrative(),
|
||||||
theQueryParams.getFilter(),
|
theQueryParams.getFilter(),
|
||||||
theQueryParams.getTypes(),
|
theQueryParams.getTypes(),
|
||||||
|
theQueryParams.getMdmExpand(),
|
||||||
theRequestDetails);
|
theRequestDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +179,7 @@ public class JpaResourceDaoPatient<T extends IBaseResource> extends BaseHapiFhir
|
||||||
theQueryParams.getNarrative(),
|
theQueryParams.getNarrative(),
|
||||||
theQueryParams.getFilter(),
|
theQueryParams.getFilter(),
|
||||||
theQueryParams.getTypes(),
|
theQueryParams.getTypes(),
|
||||||
|
theQueryParams.getMdmExpand(),
|
||||||
theRequestDetails);
|
theRequestDetails);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,16 +57,22 @@ public interface IMdmLinkJpaRepository
|
||||||
nativeQuery = true)
|
nativeQuery = true)
|
||||||
void deleteLinksHistoryWithAnyReferenceToPids(@Param("goldenPids") List<Long> theResourcePids);
|
void deleteLinksHistoryWithAnyReferenceToPids(@Param("goldenPids") List<Long> theResourcePids);
|
||||||
|
|
||||||
@Query("SELECT ml2.myGoldenResourcePid as goldenPid, ml2.mySourcePid as sourcePid FROM MdmLink ml2 "
|
// TODO: LD: the calling code in JpaBulkExportProcessor doesn't yet leverage the partition IDs, but maybe it
|
||||||
+ "WHERE ml2.myMatchResult=:matchResult "
|
// should?
|
||||||
+ "AND ml2.myGoldenResourcePid IN ("
|
@Query(
|
||||||
+ "SELECT ml.myGoldenResourcePid FROM MdmLink ml "
|
"SELECT lookup_links.myGoldenResourcePid as goldenPid, gld_rt.myPartitionIdValue as goldenPartitionId, lookup_links.mySourcePid as sourcePid, lookup_links.myPartitionIdValue as sourcePartitionId "
|
||||||
+ "INNER JOIN ResourceLink hrl "
|
+ "FROM MdmLink lookup_links "
|
||||||
+ "ON hrl.myTargetResourcePid=ml.mySourcePid "
|
+ "INNER JOIN ResourceTable gld_rt "
|
||||||
+ "AND hrl.mySourceResourcePid=:groupPid "
|
+ "on lookup_links.myGoldenResourcePid=gld_rt.myId "
|
||||||
+ "AND hrl.mySourcePath='Group.member.entity' "
|
+ "WHERE lookup_links.myMatchResult=:matchResult "
|
||||||
+ "AND hrl.myTargetResourceType='Patient'"
|
+ "AND lookup_links.myGoldenResourcePid IN ("
|
||||||
+ ")")
|
+ "SELECT inner_mdm_link.myGoldenResourcePid FROM MdmLink inner_mdm_link "
|
||||||
|
+ "INNER JOIN ResourceLink inner_res_link "
|
||||||
|
+ "ON inner_res_link.myTargetResourcePid=inner_mdm_link.mySourcePid "
|
||||||
|
+ "AND inner_res_link.mySourceResourcePid=:groupPid "
|
||||||
|
+ "AND inner_res_link.mySourcePath='Group.member.entity' "
|
||||||
|
+ "AND inner_res_link.myTargetResourceType='Patient'"
|
||||||
|
+ ")")
|
||||||
List<MdmPidTuple> expandPidsFromGroupPidGivenMatchResult(
|
List<MdmPidTuple> expandPidsFromGroupPidGivenMatchResult(
|
||||||
@Param("groupPid") Long theGroupPid, @Param("matchResult") MdmMatchResultEnum theMdmMatchResultEnum);
|
@Param("groupPid") Long theGroupPid, @Param("matchResult") MdmMatchResultEnum theMdmMatchResultEnum);
|
||||||
|
|
||||||
|
@ -77,15 +83,23 @@ public interface IMdmLinkJpaRepository
|
||||||
interface MdmPidTuple {
|
interface MdmPidTuple {
|
||||||
Long getGoldenPid();
|
Long getGoldenPid();
|
||||||
|
|
||||||
|
Integer getGoldenPartitionId();
|
||||||
|
|
||||||
Long getSourcePid();
|
Long getSourcePid();
|
||||||
|
|
||||||
|
Integer getSourcePartitionId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query("SELECT ml.myGoldenResourcePid as goldenPid, ml.mySourcePid as sourcePid " + "FROM MdmLink ml "
|
@Query(
|
||||||
+ "INNER JOIN MdmLink ml2 "
|
"SELECT lookup_link.myGoldenResourcePid as goldenPid, gld_rt.myPartitionIdValue as goldenPartitionId, lookup_link.mySourcePid as sourcePid, lookup_link.myPartitionIdValue as sourcePartitionId "
|
||||||
+ "on ml.myGoldenResourcePid=ml2.myGoldenResourcePid "
|
+ "FROM MdmLink lookup_link "
|
||||||
+ "WHERE ml2.mySourcePid=:sourcePid "
|
+ "INNER JOIN MdmLink gld_link "
|
||||||
+ "AND ml2.myMatchResult=:matchResult "
|
+ "on lookup_link.myGoldenResourcePid=gld_link.myGoldenResourcePid "
|
||||||
+ "AND ml.myMatchResult=:matchResult")
|
+ "INNER JOIN ResourceTable gld_rt "
|
||||||
|
+ "on gld_link.myGoldenResourcePid=gld_rt.myId "
|
||||||
|
+ "WHERE gld_link.mySourcePid=:sourcePid "
|
||||||
|
+ "AND gld_link.myMatchResult=:matchResult "
|
||||||
|
+ "AND lookup_link.myMatchResult=:matchResult")
|
||||||
List<MdmPidTuple> expandPidsBySourcePidAndMatchResult(
|
List<MdmPidTuple> expandPidsBySourcePidAndMatchResult(
|
||||||
@Param("sourcePid") Long theSourcePid, @Param("matchResult") MdmMatchResultEnum theMdmMatchResultEnum);
|
@Param("sourcePid") Long theSourcePid, @Param("matchResult") MdmMatchResultEnum theMdmMatchResultEnum);
|
||||||
|
|
||||||
|
@ -99,7 +113,12 @@ public interface IMdmLinkJpaRepository
|
||||||
@Param("matchResult") MdmMatchResultEnum theMdmMatchResultEnumToExclude);
|
@Param("matchResult") MdmMatchResultEnum theMdmMatchResultEnumToExclude);
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT ml.myGoldenResourcePid as goldenPid, ml.mySourcePid as sourcePid FROM MdmLink ml WHERE ml.myGoldenResourcePid = :goldenPid and ml.myMatchResult = :matchResult")
|
"SELECT lookup_link.myGoldenResourcePid as goldenPid, gld_rt.myPartitionIdValue as goldenPartitionId, lookup_link.mySourcePid as sourcePid, lookup_link.myPartitionIdValue as sourcePartitionId "
|
||||||
|
+ "FROM MdmLink lookup_link "
|
||||||
|
+ "INNER JOIN ResourceTable gld_rt "
|
||||||
|
+ "on lookup_link.myGoldenResourcePid=gld_rt.myId "
|
||||||
|
+ "WHERE lookup_link.myGoldenResourcePid = :goldenPid "
|
||||||
|
+ "AND lookup_link.myMatchResult = :matchResult")
|
||||||
List<MdmPidTuple> expandPidsByGoldenResourcePidAndMatchResult(
|
List<MdmPidTuple> expandPidsByGoldenResourcePidAndMatchResult(
|
||||||
@Param("goldenPid") Long theSourcePid, @Param("matchResult") MdmMatchResultEnum theMdmMatchResultEnum);
|
@Param("goldenPid") Long theSourcePid, @Param("matchResult") MdmMatchResultEnum theMdmMatchResultEnum);
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,8 @@ import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.IdType;
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
|
@ -98,6 +100,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class IdHelperService implements IIdHelperService<JpaPid> {
|
public class IdHelperService implements IIdHelperService<JpaPid> {
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(IdHelperService.class);
|
||||||
public static final Predicate[] EMPTY_PREDICATE_ARRAY = new Predicate[0];
|
public static final Predicate[] EMPTY_PREDICATE_ARRAY = new Predicate[0];
|
||||||
public static final String RESOURCE_PID = "RESOURCE_PID";
|
public static final String RESOURCE_PID = "RESOURCE_PID";
|
||||||
|
|
||||||
|
@ -210,9 +213,6 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
||||||
Validate.isTrue(!theIds.isEmpty(), "theIds must not be empty");
|
Validate.isTrue(!theIds.isEmpty(), "theIds must not be empty");
|
||||||
|
|
||||||
Map<String, JpaPid> retVals = new HashMap<>();
|
Map<String, JpaPid> retVals = new HashMap<>();
|
||||||
RequestPartitionId partitionId = myPartitionSettings.isAllowUnqualifiedCrossPartitionReference()
|
|
||||||
? RequestPartitionId.allPartitions()
|
|
||||||
: theRequestPartitionId;
|
|
||||||
for (String id : theIds) {
|
for (String id : theIds) {
|
||||||
JpaPid retVal;
|
JpaPid retVal;
|
||||||
if (!idRequiresForcedId(id)) {
|
if (!idRequiresForcedId(id)) {
|
||||||
|
@ -223,17 +223,18 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
||||||
// is a forced id
|
// is a forced id
|
||||||
// we must resolve!
|
// we must resolve!
|
||||||
if (myStorageSettings.isDeleteEnabled()) {
|
if (myStorageSettings.isDeleteEnabled()) {
|
||||||
retVal = resolveResourceIdentity(partitionId, theResourceType, id, theExcludeDeleted)
|
retVal = resolveResourceIdentity(theRequestPartitionId, theResourceType, id, theExcludeDeleted)
|
||||||
.getPersistentId();
|
.getPersistentId();
|
||||||
retVals.put(id, retVal);
|
retVals.put(id, retVal);
|
||||||
} else {
|
} else {
|
||||||
// fetch from cache... adding to cache if not available
|
// fetch from cache... adding to cache if not available
|
||||||
String key = toForcedIdToPidKey(partitionId, theResourceType, id);
|
String key = toForcedIdToPidKey(theRequestPartitionId, theResourceType, id);
|
||||||
retVal = myMemoryCacheService.getThenPutAfterCommit(
|
retVal = myMemoryCacheService.getThenPutAfterCommit(
|
||||||
MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key, t -> {
|
MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key, t -> {
|
||||||
List<IIdType> ids = Collections.singletonList(new IdType(theResourceType, id));
|
List<IIdType> ids = Collections.singletonList(new IdType(theResourceType, id));
|
||||||
// fetches from cache using a function that checks cache first...
|
// fetches from cache using a function that checks cache first...
|
||||||
List<JpaPid> resolvedIds = resolveResourcePersistentIdsWithCache(partitionId, ids);
|
List<JpaPid> resolvedIds =
|
||||||
|
resolveResourcePersistentIdsWithCache(theRequestPartitionId, ids);
|
||||||
if (resolvedIds.isEmpty()) {
|
if (resolvedIds.isEmpty()) {
|
||||||
throw new ResourceNotFoundException(Msg.code(1100) + ids.get(0));
|
throw new ResourceNotFoundException(Msg.code(1100) + ids.get(0));
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,8 +120,11 @@ public class MdmLinkDaoJpaImpl implements IMdmLinkDao<JpaPid, MdmLink> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private MdmPidTuple<JpaPid> daoTupleToMdmTuple(IMdmLinkJpaRepository.MdmPidTuple theMdmPidTuple) {
|
private MdmPidTuple<JpaPid> daoTupleToMdmTuple(IMdmLinkJpaRepository.MdmPidTuple theMdmPidTuple) {
|
||||||
return MdmPidTuple.fromGoldenAndSource(
|
return MdmPidTuple.fromGoldenAndSourceAndPartitionIds(
|
||||||
JpaPid.fromId(theMdmPidTuple.getGoldenPid()), JpaPid.fromId(theMdmPidTuple.getSourcePid()));
|
JpaPid.fromId(theMdmPidTuple.getGoldenPid()),
|
||||||
|
theMdmPidTuple.getGoldenPartitionId(),
|
||||||
|
JpaPid.fromId(theMdmPidTuple.getSourcePid()),
|
||||||
|
theMdmPidTuple.getSourcePartitionId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -112,6 +112,11 @@ public abstract class BaseJpaResourceProviderPatient<T extends IBaseResource> ex
|
||||||
max = OperationParam.MAX_UNLIMITED,
|
max = OperationParam.MAX_UNLIMITED,
|
||||||
typeName = "string")
|
typeName = "string")
|
||||||
List<IPrimitiveType<String>> theTypes,
|
List<IPrimitiveType<String>> theTypes,
|
||||||
|
@Description(
|
||||||
|
shortDefinition =
|
||||||
|
"Filter the resources to return only resources matching the given _type filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)")
|
||||||
|
@OperationParam(name = Constants.PARAM_MDM, min = 0, max = 1, typeName = "boolean")
|
||||||
|
IPrimitiveType<Boolean> theMdmExpand,
|
||||||
@Sort SortSpec theSortSpec,
|
@Sort SortSpec theSortSpec,
|
||||||
RequestDetails theRequestDetails) {
|
RequestDetails theRequestDetails) {
|
||||||
|
|
||||||
|
@ -126,6 +131,7 @@ public abstract class BaseJpaResourceProviderPatient<T extends IBaseResource> ex
|
||||||
everythingParams.setNarrative(toStringAndList(theNarrative));
|
everythingParams.setNarrative(toStringAndList(theNarrative));
|
||||||
everythingParams.setFilter(toStringAndList(theFilter));
|
everythingParams.setFilter(toStringAndList(theFilter));
|
||||||
everythingParams.setTypes(toStringAndList(theTypes));
|
everythingParams.setTypes(toStringAndList(theTypes));
|
||||||
|
everythingParams.setMdmExpand(resolveNullValue(theMdmExpand));
|
||||||
|
|
||||||
return ((IFhirResourceDaoPatient<?>) getDao())
|
return ((IFhirResourceDaoPatient<?>) getDao())
|
||||||
.patientInstanceEverything(theServletRequest, theRequestDetails, everythingParams, theId);
|
.patientInstanceEverything(theServletRequest, theRequestDetails, everythingParams, theId);
|
||||||
|
@ -202,6 +208,11 @@ public abstract class BaseJpaResourceProviderPatient<T extends IBaseResource> ex
|
||||||
max = OperationParam.MAX_UNLIMITED,
|
max = OperationParam.MAX_UNLIMITED,
|
||||||
typeName = "id")
|
typeName = "id")
|
||||||
List<IIdType> theId,
|
List<IIdType> theId,
|
||||||
|
@Description(
|
||||||
|
shortDefinition =
|
||||||
|
"Filter the resources to return only resources matching the given _type filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)")
|
||||||
|
@OperationParam(name = Constants.PARAM_MDM, min = 0, max = 1, typeName = "boolean")
|
||||||
|
IPrimitiveType<Boolean> theMdmExpand,
|
||||||
@Sort SortSpec theSortSpec,
|
@Sort SortSpec theSortSpec,
|
||||||
RequestDetails theRequestDetails) {
|
RequestDetails theRequestDetails) {
|
||||||
|
|
||||||
|
@ -216,6 +227,7 @@ public abstract class BaseJpaResourceProviderPatient<T extends IBaseResource> ex
|
||||||
everythingParams.setNarrative(toStringAndList(theNarrative));
|
everythingParams.setNarrative(toStringAndList(theNarrative));
|
||||||
everythingParams.setFilter(toStringAndList(theFilter));
|
everythingParams.setFilter(toStringAndList(theFilter));
|
||||||
everythingParams.setTypes(toStringAndList(theTypes));
|
everythingParams.setTypes(toStringAndList(theTypes));
|
||||||
|
everythingParams.setMdmExpand(resolveNullValue(theMdmExpand));
|
||||||
|
|
||||||
return ((IFhirResourceDaoPatient<?>) getDao())
|
return ((IFhirResourceDaoPatient<?>) getDao())
|
||||||
.patientTypeEverything(
|
.patientTypeEverything(
|
||||||
|
@ -261,4 +273,8 @@ public abstract class BaseJpaResourceProviderPatient<T extends IBaseResource> ex
|
||||||
}
|
}
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean resolveNullValue(IPrimitiveType<Boolean> theMdmExpand) {
|
||||||
|
return theMdmExpand == null ? Boolean.FALSE : theMdmExpand.getValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,7 +176,7 @@ public class JpaBulkExportProcessorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private MdmPidTuple<JpaPid> createTuple(long theGroupId, long theGoldenId) {
|
private MdmPidTuple<JpaPid> createTuple(long theGroupId, long theGoldenId) {
|
||||||
return MdmPidTuple.fromGoldenAndSource(JpaPid.fromId(theGoldenId), JpaPid.fromId(theGroupId));
|
return MdmPidTuple.fromGoldenAndSourceAndPartitionIds(JpaPid.fromId(theGoldenId), null, JpaPid.fromId(theGroupId), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
|
|
|
@ -8,11 +8,11 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
import jakarta.persistence.Tuple;
|
import jakarta.persistence.Tuple;
|
||||||
import jakarta.persistence.TypedQuery;
|
import jakarta.persistence.criteria.Path;
|
||||||
import jakarta.persistence.criteria.*;
|
import jakarta.persistence.criteria.Root;
|
||||||
import org.hl7.fhir.r4.hapi.ctx.FhirR4;
|
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -22,9 +22,6 @@ import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -60,15 +57,14 @@ public class IdHelperServiceTest {
|
||||||
void setUp() {
|
void setUp() {
|
||||||
subject.setDontCheckActiveTransactionForUnitTest(true);
|
subject.setDontCheckActiveTransactionForUnitTest(true);
|
||||||
|
|
||||||
when(myStorageSettings.isDeleteEnabled()).thenReturn(true);
|
when(myStorageSettings.isDeleteEnabled()).thenReturn(true);
|
||||||
when(myStorageSettings.getResourceClientIdStrategy()).thenReturn(JpaStorageSettings.ClientIdStrategyEnum.ANY);
|
when(myStorageSettings.getResourceClientIdStrategy()).thenReturn(JpaStorageSettings.ClientIdStrategyEnum.ANY);
|
||||||
when(myPartitionSettings.isAllowUnqualifiedCrossPartitionReference()).thenReturn(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResolveResourcePersistentIds() {
|
public void testResolveResourcePersistentIds() {
|
||||||
//prepare params
|
//prepare params
|
||||||
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionName("Partition-A");
|
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionIdAndName(1, "Partition-A");
|
||||||
String resourceType = "Patient";
|
String resourceType = "Patient";
|
||||||
Long id = 123L;
|
Long id = 123L;
|
||||||
List<String> ids = List.of(String.valueOf(id));
|
List<String> ids = List.of(String.valueOf(id));
|
||||||
|
@ -77,25 +73,18 @@ public class IdHelperServiceTest {
|
||||||
//prepare results
|
//prepare results
|
||||||
Patient expectedPatient = new Patient();
|
Patient expectedPatient = new Patient();
|
||||||
expectedPatient.setId(ids.get(0));
|
expectedPatient.setId(ids.get(0));
|
||||||
Object[] obj = new Object[] {resourceType, Long.parseLong(ids.get(0)), ids.get(0), Date.from(Instant.now())};
|
|
||||||
|
|
||||||
// configure mock behaviour
|
// configure mock behaviour
|
||||||
when(myStorageSettings.isDeleteEnabled()).thenReturn(true);
|
when(myStorageSettings.isDeleteEnabled()).thenReturn(true);
|
||||||
when(myResourceTableDao
|
|
||||||
.findAndResolveByForcedIdWithNoType(eq(resourceType), eq(ids), eq(theExcludeDeleted)))
|
|
||||||
.thenReturn(Collections.singletonList(obj));
|
|
||||||
|
|
||||||
Map<String, JpaPid> actualIds = subject.resolveResourcePersistentIds(requestPartitionId, resourceType, ids, theExcludeDeleted);
|
final ResourceNotFoundException resourceNotFoundException = assertThrows(ResourceNotFoundException.class, () -> subject.resolveResourcePersistentIds(requestPartitionId, resourceType, ids, theExcludeDeleted));
|
||||||
|
assertEquals("HAPI-2001: Resource Patient/123 is not known", resourceNotFoundException.getMessage());
|
||||||
//verify results
|
|
||||||
assertFalse(actualIds.isEmpty());
|
|
||||||
assertEquals(id, actualIds.get(ids.get(0)).getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResolveResourcePersistentIdsDeleteFalse() {
|
public void testResolveResourcePersistentIdsDeleteFalse() {
|
||||||
//prepare Params
|
//prepare Params
|
||||||
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionName("Partition-A");
|
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionIdAndName(1, "Partition-A");
|
||||||
Long id = 123L;
|
Long id = 123L;
|
||||||
String resourceType = "Patient";
|
String resourceType = "Patient";
|
||||||
List<String> ids = List.of(String.valueOf(id));
|
List<String> ids = List.of(String.valueOf(id));
|
||||||
|
@ -107,54 +96,21 @@ public class IdHelperServiceTest {
|
||||||
expectedPatient.setId(ids.get(0));
|
expectedPatient.setId(ids.get(0));
|
||||||
|
|
||||||
// configure mock behaviour
|
// configure mock behaviour
|
||||||
configureCacheBehaviour(forcedId);
|
|
||||||
configureEntityManagerBehaviour(id, resourceType, ids.get(0));
|
|
||||||
when(myStorageSettings.isDeleteEnabled()).thenReturn(false);
|
when(myStorageSettings.isDeleteEnabled()).thenReturn(false);
|
||||||
when(myFhirCtx.getVersion()).thenReturn(new FhirR4());
|
|
||||||
|
|
||||||
Map<String, JpaPid> actualIds = subject.resolveResourcePersistentIds(requestPartitionId, resourceType, ids, theExcludeDeleted);
|
Map<String, JpaPid> actualIds = subject.resolveResourcePersistentIds(requestPartitionId, resourceType, ids, theExcludeDeleted);
|
||||||
|
|
||||||
//verifyResult
|
//verifyResult
|
||||||
assertFalse(actualIds.isEmpty());
|
assertFalse(actualIds.isEmpty());
|
||||||
assertEquals(id, actualIds.get(ids.get(0)).getId());
|
assertNull(actualIds.get(ids.get(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureCacheBehaviour(String resourceUrl) {
|
|
||||||
when(myMemoryCacheService.getThenPutAfterCommit(eq(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID), eq(resourceUrl), any())).thenCallRealMethod();
|
|
||||||
doNothing().when(myMemoryCacheService).putAfterCommit(eq(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID), eq(resourceUrl), ArgumentMatchers.<JpaPid>any());
|
|
||||||
when(myMemoryCacheService.getIfPresent(eq(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID), eq(resourceUrl))).thenReturn(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void configureEntityManagerBehaviour(Long idNumber, String resourceType, String id) {
|
|
||||||
List<Tuple> mockedTupleList = getMockedTupleList(idNumber, resourceType, id);
|
|
||||||
CriteriaBuilder builder = getMockedCriteriaBuilder();
|
|
||||||
Root<ResourceTable> from = getMockedFrom();
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
TypedQuery<Tuple> query = (TypedQuery<Tuple>) mock(TypedQuery.class);
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
CriteriaQuery<Tuple> cq = mock(CriteriaQuery.class);
|
|
||||||
|
|
||||||
when(builder.createTupleQuery()).thenReturn(cq);
|
|
||||||
when(cq.from(ArgumentMatchers.<Class<ResourceTable>>any())).thenReturn(from);
|
|
||||||
when(query.getResultList()).thenReturn(mockedTupleList);
|
|
||||||
|
|
||||||
when(myEntityManager.getCriteriaBuilder()).thenReturn(builder);
|
|
||||||
when(myEntityManager.createQuery(ArgumentMatchers.<CriteriaQuery<Tuple>>any())).thenReturn(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CriteriaBuilder getMockedCriteriaBuilder() {
|
|
||||||
Predicate pred = mock(Predicate.class);
|
|
||||||
CriteriaBuilder builder = mock(CriteriaBuilder.class);
|
|
||||||
lenient().when(builder.equal(any(), any())).thenReturn(pred);
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
private Root<ResourceTable> getMockedFrom() {
|
private Root<ResourceTable> getMockedFrom() {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Path<Object> path = mock(Path.class);
|
Path<Object> path = mock(Path.class);
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Root<ResourceTable> from = mock(Root.class);
|
Root<ResourceTable> from = mock(Root.class);
|
||||||
lenient().when(from.get(ArgumentMatchers.<String>any())).thenReturn(path);
|
when(from.get(ArgumentMatchers.<String>any())).thenReturn(path);
|
||||||
return from;
|
return from;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -229,7 +229,6 @@ public class MdmSearchExpandingInterceptorIT extends BaseMdmR4Test {
|
||||||
String id = expectedIds.get(0);
|
String id = expectedIds.get(0);
|
||||||
|
|
||||||
HashMap<String, String[]> queryParameters = new HashMap<>();
|
HashMap<String, String[]> queryParameters = new HashMap<>();
|
||||||
queryParameters.put("_mdm", new String[]{"true"});
|
|
||||||
|
|
||||||
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
|
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
|
||||||
RequestDetails theDetails = Mockito.mock(RequestDetails.class);
|
RequestDetails theDetails = Mockito.mock(RequestDetails.class);
|
||||||
|
@ -240,9 +239,11 @@ public class MdmSearchExpandingInterceptorIT extends BaseMdmR4Test {
|
||||||
// test
|
// test
|
||||||
myStorageSettings.setAllowMdmExpansion(true);
|
myStorageSettings.setAllowMdmExpansion(true);
|
||||||
IFhirResourceDaoPatient<Patient> dao = (IFhirResourceDaoPatient<Patient>) myPatientDao;
|
IFhirResourceDaoPatient<Patient> dao = (IFhirResourceDaoPatient<Patient>) myPatientDao;
|
||||||
|
final PatientEverythingParameters queryParams = new PatientEverythingParameters();
|
||||||
|
queryParams.setMdmExpand(true);
|
||||||
IBundleProvider outcome = dao.patientInstanceEverything(
|
IBundleProvider outcome = dao.patientInstanceEverything(
|
||||||
req,
|
req,
|
||||||
theDetails, new PatientEverythingParameters(), new IdDt(id)
|
theDetails, queryParams, new IdDt(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
// verify return results
|
// verify return results
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
*/
|
*/
|
||||||
package ca.uhn.fhir.mdm.api;
|
package ca.uhn.fhir.mdm.api;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
@ -27,15 +28,18 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public interface IMdmLinkExpandSvc {
|
public interface IMdmLinkExpandSvc {
|
||||||
Set<String> expandMdmBySourceResource(IBaseResource theResource);
|
Set<String> expandMdmBySourceResource(RequestPartitionId theRequestPartitionId, IBaseResource theResource);
|
||||||
|
|
||||||
Set<String> expandMdmBySourceResourceId(IIdType theId);
|
Set<String> expandMdmBySourceResourceId(RequestPartitionId theRequestPartitionId, IIdType theId);
|
||||||
|
|
||||||
Set<String> expandMdmBySourceResourcePid(IResourcePersistentId theSourceResourcePid);
|
Set<String> expandMdmBySourceResourcePid(
|
||||||
|
RequestPartitionId theRequestPartitionId, IResourcePersistentId<?> theSourceResourcePid);
|
||||||
|
|
||||||
Set<String> expandMdmByGoldenResourceId(IResourcePersistentId theGoldenResourcePid);
|
Set<String> expandMdmByGoldenResourceId(
|
||||||
|
RequestPartitionId theRequestPartitionId, IResourcePersistentId<?> theGoldenResourcePid);
|
||||||
|
|
||||||
Set<String> expandMdmByGoldenResourcePid(IResourcePersistentId theGoldenResourcePid);
|
Set<String> expandMdmByGoldenResourcePid(
|
||||||
|
RequestPartitionId theRequestPartitionId, IResourcePersistentId<?> theGoldenResourcePid);
|
||||||
|
|
||||||
Set<String> expandMdmByGoldenResourceId(IdDt theId);
|
Set<String> expandMdmByGoldenResourceId(RequestPartitionId theRequestPartitionId, IdDt theId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,16 @@ package ca.uhn.fhir.mdm.interceptor;
|
||||||
import ca.uhn.fhir.interceptor.api.Hook;
|
import ca.uhn.fhir.interceptor.api.Hook;
|
||||||
import ca.uhn.fhir.interceptor.api.Interceptor;
|
import ca.uhn.fhir.interceptor.api.Interceptor;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
|
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.mdm.api.IMdmLinkExpandSvc;
|
import ca.uhn.fhir.mdm.api.IMdmLinkExpandSvc;
|
||||||
import ca.uhn.fhir.mdm.log.Logs;
|
import ca.uhn.fhir.mdm.log.Logs;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
@ -52,6 +56,9 @@ public class MdmSearchExpandingInterceptor {
|
||||||
|
|
||||||
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
|
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IMdmLinkExpandSvc myMdmLinkExpandSvc;
|
private IMdmLinkExpandSvc myMdmLinkExpandSvc;
|
||||||
|
|
||||||
|
@ -59,15 +66,20 @@ public class MdmSearchExpandingInterceptor {
|
||||||
private JpaStorageSettings myStorageSettings;
|
private JpaStorageSettings myStorageSettings;
|
||||||
|
|
||||||
@Hook(Pointcut.STORAGE_PRESEARCH_REGISTERED)
|
@Hook(Pointcut.STORAGE_PRESEARCH_REGISTERED)
|
||||||
public void hook(SearchParameterMap theSearchParameterMap) {
|
public void hook(RequestDetails theRequestDetails, SearchParameterMap theSearchParameterMap) {
|
||||||
if (myStorageSettings.isAllowMdmExpansion()) {
|
if (myStorageSettings.isAllowMdmExpansion()) {
|
||||||
|
final RequestDetails requestDetailsToUse =
|
||||||
|
theRequestDetails == null ? new SystemRequestDetails() : theRequestDetails;
|
||||||
|
final RequestPartitionId requestPartitionId =
|
||||||
|
myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(
|
||||||
|
requestDetailsToUse, requestDetailsToUse.getResourceName(), theSearchParameterMap, null);
|
||||||
for (Map.Entry<String, List<List<IQueryParameterType>>> set : theSearchParameterMap.entrySet()) {
|
for (Map.Entry<String, List<List<IQueryParameterType>>> set : theSearchParameterMap.entrySet()) {
|
||||||
String paramName = set.getKey();
|
String paramName = set.getKey();
|
||||||
List<List<IQueryParameterType>> andList = set.getValue();
|
List<List<IQueryParameterType>> andList = set.getValue();
|
||||||
for (List<IQueryParameterType> orList : andList) {
|
for (List<IQueryParameterType> orList : andList) {
|
||||||
// here we will know if it's an _id param or not
|
// here we will know if it's an _id param or not
|
||||||
// from theSearchParameterMap.keySet()
|
// from theSearchParameterMap.keySet()
|
||||||
expandAnyReferenceParameters(paramName, orList);
|
expandAnyReferenceParameters(requestPartitionId, paramName, orList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,7 +88,8 @@ public class MdmSearchExpandingInterceptor {
|
||||||
/**
|
/**
|
||||||
* If a Parameter is a reference parameter, and it has been set to expand MDM, perform the expansion.
|
* If a Parameter is a reference parameter, and it has been set to expand MDM, perform the expansion.
|
||||||
*/
|
*/
|
||||||
private void expandAnyReferenceParameters(String theParamName, List<IQueryParameterType> orList) {
|
private void expandAnyReferenceParameters(
|
||||||
|
RequestPartitionId theRequestPartitionId, String theParamName, List<IQueryParameterType> orList) {
|
||||||
List<IQueryParameterType> toRemove = new ArrayList<>();
|
List<IQueryParameterType> toRemove = new ArrayList<>();
|
||||||
List<IQueryParameterType> toAdd = new ArrayList<>();
|
List<IQueryParameterType> toAdd = new ArrayList<>();
|
||||||
for (IQueryParameterType iQueryParameterType : orList) {
|
for (IQueryParameterType iQueryParameterType : orList) {
|
||||||
|
@ -85,13 +98,13 @@ public class MdmSearchExpandingInterceptor {
|
||||||
if (refParam.isMdmExpand()) {
|
if (refParam.isMdmExpand()) {
|
||||||
ourLog.debug("Found a reference parameter to expand: {}", refParam);
|
ourLog.debug("Found a reference parameter to expand: {}", refParam);
|
||||||
// First, attempt to expand as a source resource.
|
// First, attempt to expand as a source resource.
|
||||||
Set<String> expandedResourceIds =
|
Set<String> expandedResourceIds = myMdmLinkExpandSvc.expandMdmBySourceResourceId(
|
||||||
myMdmLinkExpandSvc.expandMdmBySourceResourceId(new IdDt(refParam.getValue()));
|
theRequestPartitionId, new IdDt(refParam.getValue()));
|
||||||
|
|
||||||
// If we failed, attempt to expand as a golden resource
|
// If we failed, attempt to expand as a golden resource
|
||||||
if (expandedResourceIds.isEmpty()) {
|
if (expandedResourceIds.isEmpty()) {
|
||||||
expandedResourceIds =
|
expandedResourceIds = myMdmLinkExpandSvc.expandMdmByGoldenResourceId(
|
||||||
myMdmLinkExpandSvc.expandMdmByGoldenResourceId(new IdDt(refParam.getValue()));
|
theRequestPartitionId, new IdDt(refParam.getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebuild the search param list.
|
// Rebuild the search param list.
|
||||||
|
@ -105,7 +118,7 @@ public class MdmSearchExpandingInterceptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (theParamName.equalsIgnoreCase("_id")) {
|
} else if (theParamName.equalsIgnoreCase("_id")) {
|
||||||
expandIdParameter(iQueryParameterType, toAdd, toRemove);
|
expandIdParameter(theRequestPartitionId, iQueryParameterType, toAdd, toRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,11 +138,13 @@ public class MdmSearchExpandingInterceptor {
|
||||||
* Expands out the provided _id parameter into all the various
|
* Expands out the provided _id parameter into all the various
|
||||||
* ids of linked resources.
|
* ids of linked resources.
|
||||||
*
|
*
|
||||||
|
* @param theRequestPartitionId
|
||||||
* @param theIdParameter
|
* @param theIdParameter
|
||||||
* @param theAddList
|
* @param theAddList
|
||||||
* @param theRemoveList
|
* @param theRemoveList
|
||||||
*/
|
*/
|
||||||
private void expandIdParameter(
|
private void expandIdParameter(
|
||||||
|
RequestPartitionId theRequestPartitionId,
|
||||||
IQueryParameterType theIdParameter,
|
IQueryParameterType theIdParameter,
|
||||||
List<IQueryParameterType> theAddList,
|
List<IQueryParameterType> theAddList,
|
||||||
List<IQueryParameterType> theRemoveList) {
|
List<IQueryParameterType> theRemoveList) {
|
||||||
|
@ -157,10 +172,10 @@ public class MdmSearchExpandingInterceptor {
|
||||||
} else if (mdmExpand) {
|
} else if (mdmExpand) {
|
||||||
ourLog.debug("_id parameter must be expanded out from: {}", id.getValue());
|
ourLog.debug("_id parameter must be expanded out from: {}", id.getValue());
|
||||||
|
|
||||||
Set<String> expandedResourceIds = myMdmLinkExpandSvc.expandMdmBySourceResourceId(id);
|
Set<String> expandedResourceIds = myMdmLinkExpandSvc.expandMdmBySourceResourceId(theRequestPartitionId, id);
|
||||||
|
|
||||||
if (expandedResourceIds.isEmpty()) {
|
if (expandedResourceIds.isEmpty()) {
|
||||||
expandedResourceIds = myMdmLinkExpandSvc.expandMdmByGoldenResourceId((IdDt) id);
|
expandedResourceIds = myMdmLinkExpandSvc.expandMdmByGoldenResourceId(theRequestPartitionId, (IdDt) id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebuild
|
// Rebuild
|
||||||
|
|
|
@ -20,25 +20,83 @@
|
||||||
package ca.uhn.fhir.mdm.model;
|
package ca.uhn.fhir.mdm.model;
|
||||||
|
|
||||||
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
public class MdmPidTuple<T extends IResourcePersistentId> {
|
public class MdmPidTuple<T extends IResourcePersistentId> {
|
||||||
private final T myGoldenPid;
|
private final T myGoldenPid;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final Integer myGoldenPartitionId;
|
||||||
|
|
||||||
private final T mySourcePid;
|
private final T mySourcePid;
|
||||||
|
|
||||||
private MdmPidTuple(T theGoldenPid, T theSourcePid) {
|
@Nullable
|
||||||
|
private final Integer mySourcePartitionId;
|
||||||
|
|
||||||
|
private MdmPidTuple(
|
||||||
|
T theGoldenPid,
|
||||||
|
@Nullable Integer theGoldenPartitionId,
|
||||||
|
T theSourcePid,
|
||||||
|
@Nullable Integer theSourcePartitionId) {
|
||||||
myGoldenPid = theGoldenPid;
|
myGoldenPid = theGoldenPid;
|
||||||
mySourcePid = theSourcePid;
|
mySourcePid = theSourcePid;
|
||||||
|
myGoldenPartitionId = theGoldenPartitionId;
|
||||||
|
mySourcePartitionId = theSourcePartitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <P extends IResourcePersistentId> MdmPidTuple<P> fromGoldenAndSource(P theGoldenPid, P theSourcePid) {
|
public static <P extends IResourcePersistentId> MdmPidTuple<P> fromGoldenAndSource(P theGoldenPid, P theSourcePid) {
|
||||||
return new MdmPidTuple<>(theGoldenPid, theSourcePid);
|
return new MdmPidTuple<>(theGoldenPid, null, theSourcePid, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <P extends IResourcePersistentId> MdmPidTuple<P> fromGoldenAndSourceAndPartitionIds(
|
||||||
|
P theGoldenPid, Integer theGoldenPartitionId, P theSourcePid, Integer theSourcePartitionId) {
|
||||||
|
return new MdmPidTuple<>(theGoldenPid, theGoldenPartitionId, theSourcePid, theSourcePartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public T getGoldenPid() {
|
public T getGoldenPid() {
|
||||||
return myGoldenPid;
|
return myGoldenPid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Integer getGoldenPartitionId() {
|
||||||
|
return myGoldenPartitionId;
|
||||||
|
}
|
||||||
|
|
||||||
public T getSourcePid() {
|
public T getSourcePid() {
|
||||||
return mySourcePid;
|
return mySourcePid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Integer getSourcePartitionId() {
|
||||||
|
return mySourcePartitionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
MdmPidTuple<?> that = (MdmPidTuple<?>) o;
|
||||||
|
return Objects.equals(myGoldenPid, that.myGoldenPid)
|
||||||
|
&& Objects.equals(myGoldenPartitionId, that.myGoldenPartitionId)
|
||||||
|
&& Objects.equals(mySourcePid, that.mySourcePid)
|
||||||
|
&& Objects.equals(mySourcePartitionId, that.mySourcePartitionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(myGoldenPid, myGoldenPartitionId, mySourcePid, mySourcePartitionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new StringJoiner(", ", MdmPidTuple.class.getSimpleName() + "[", "]")
|
||||||
|
.add("myGoldenPid=" + myGoldenPid)
|
||||||
|
.add("myGoldenPartitionId=" + myGoldenPartitionId)
|
||||||
|
.add("mySourcePid=" + mySourcePid)
|
||||||
|
.add("mySourcePartitionId=" + mySourcePartitionId)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,9 +36,11 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
|
@ -61,88 +63,114 @@ public class MdmLinkExpandSvc implements IMdmLinkExpandSvc {
|
||||||
* @return A set of strings representing the FHIR IDs of the expanded resources.
|
* @return A set of strings representing the FHIR IDs of the expanded resources.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Set<String> expandMdmBySourceResource(IBaseResource theResource) {
|
public Set<String> expandMdmBySourceResource(RequestPartitionId theRequestPartitionId, IBaseResource theResource) {
|
||||||
ourLog.debug("About to MDM-expand source resource {}", theResource);
|
ourLog.debug("About to MDM-expand source resource {}", theResource);
|
||||||
return expandMdmBySourceResourceId(theResource.getIdElement());
|
return expandMdmBySourceResourceId(theRequestPartitionId, theResource.getIdElement());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a resource ID of a source resource, perform MDM expansion and return all the resource IDs of all resources that are
|
* Given a resource ID of a source resource, perform MDM expansion and return all the resource IDs of all resources that are
|
||||||
* MDM-Matched to this resource.
|
* MDM-Matched to this resource.
|
||||||
*
|
*
|
||||||
* @param theId The Resource ID of the resource to MDM-Expand
|
* @param theRequestPartitionId The partition ID associated with the request.
|
||||||
|
* @param theId The Resource ID of the resource to MDM-Expand
|
||||||
* @return A set of strings representing the FHIR ids of the expanded resources.
|
* @return A set of strings representing the FHIR ids of the expanded resources.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Set<String> expandMdmBySourceResourceId(IIdType theId) {
|
public Set<String> expandMdmBySourceResourceId(RequestPartitionId theRequestPartitionId, IIdType theId) {
|
||||||
ourLog.debug("About to expand source resource with resource id {}", theId);
|
ourLog.debug("About to expand source resource with resource id {}", theId);
|
||||||
return expandMdmBySourceResourcePid(
|
return expandMdmBySourceResourcePid(
|
||||||
|
theRequestPartitionId,
|
||||||
myIdHelperService.getPidOrThrowException(RequestPartitionId.allPartitions(), theId));
|
myIdHelperService.getPidOrThrowException(RequestPartitionId.allPartitions(), theId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a PID of a source resource, perform MDM expansion and return all the resource IDs of all resources that are
|
* Given a partition ID and a PID of a source resource, perform MDM expansion and return all the resource IDs of all resources that are
|
||||||
* MDM-Matched to this resource.
|
* MDM-Matched to this resource.
|
||||||
*
|
*
|
||||||
* @param theSourceResourcePid The PID of the resource to MDM-Expand
|
* @param theRequestPartitionId The partition ID associated with the request.
|
||||||
|
* @param theSourceResourcePid The PID of the resource to MDM-Expand
|
||||||
* @return A set of strings representing the FHIR ids of the expanded resources.
|
* @return A set of strings representing the FHIR ids of the expanded resources.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Set<String> expandMdmBySourceResourcePid(IResourcePersistentId theSourceResourcePid) {
|
public Set<String> expandMdmBySourceResourcePid(
|
||||||
|
RequestPartitionId theRequestPartitionId, IResourcePersistentId<?> theSourceResourcePid) {
|
||||||
ourLog.debug("About to expand source resource with PID {}", theSourceResourcePid);
|
ourLog.debug("About to expand source resource with PID {}", theSourceResourcePid);
|
||||||
List<MdmPidTuple> goldenPidSourcePidTuples =
|
final List<MdmPidTuple<?>> goldenPidSourcePidTuples =
|
||||||
myMdmLinkDao.expandPidsBySourcePidAndMatchResult(theSourceResourcePid, MdmMatchResultEnum.MATCH);
|
myMdmLinkDao.expandPidsBySourcePidAndMatchResult(theSourceResourcePid, MdmMatchResultEnum.MATCH);
|
||||||
return flattenPidTuplesToSet(theSourceResourcePid, goldenPidSourcePidTuples);
|
|
||||||
|
return flattenPidTuplesToSet(theRequestPartitionId, theSourceResourcePid, goldenPidSourcePidTuples);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a PID of a golden resource, perform MDM expansion and return all the resource IDs of all resources that are
|
* Given a PID of a golden resource, perform MDM expansion and return all the resource IDs of all resources that are
|
||||||
* MDM-Matched to this golden resource.
|
* MDM-Matched to this golden resource.
|
||||||
*
|
*
|
||||||
|
* @param theRequestPartitionId Partition information from the request
|
||||||
* @param theGoldenResourcePid The PID of the golden resource to MDM-Expand.
|
* @param theGoldenResourcePid The PID of the golden resource to MDM-Expand.
|
||||||
* @return A set of strings representing the FHIR ids of the expanded resources.
|
* @return A set of strings representing the FHIR ids of the expanded resources.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Set<String> expandMdmByGoldenResourceId(IResourcePersistentId theGoldenResourcePid) {
|
public Set<String> expandMdmByGoldenResourceId(
|
||||||
|
RequestPartitionId theRequestPartitionId, IResourcePersistentId<?> theGoldenResourcePid) {
|
||||||
ourLog.debug("About to expand golden resource with PID {}", theGoldenResourcePid);
|
ourLog.debug("About to expand golden resource with PID {}", theGoldenResourcePid);
|
||||||
List<MdmPidTuple> goldenPidSourcePidTuples = myMdmLinkDao.expandPidsByGoldenResourcePidAndMatchResult(
|
final List<MdmPidTuple<?>> goldenPidSourcePidTuples = myMdmLinkDao.expandPidsByGoldenResourcePidAndMatchResult(
|
||||||
theGoldenResourcePid, MdmMatchResultEnum.MATCH);
|
theGoldenResourcePid, MdmMatchResultEnum.MATCH);
|
||||||
return flattenPidTuplesToSet(theGoldenResourcePid, goldenPidSourcePidTuples);
|
return flattenPidTuplesToSet(theRequestPartitionId, theGoldenResourcePid, goldenPidSourcePidTuples);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a resource ID of a golden resource, perform MDM expansion and return all the resource IDs of all resources that are
|
* Given a resource ID of a golden resource, perform MDM expansion and return all the resource IDs of all resources that are
|
||||||
* MDM-Matched to this golden resource.
|
* MDM-Matched to this golden resource.
|
||||||
*
|
*
|
||||||
|
* @param theRequestPartitionId Partition information from the request
|
||||||
* @param theGoldenResourcePid The resource ID of the golden resource to MDM-Expand.
|
* @param theGoldenResourcePid The resource ID of the golden resource to MDM-Expand.
|
||||||
* @return A set of strings representing the FHIR ids of the expanded resources.
|
* @return A set of strings representing the FHIR ids of the expanded resources.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Set<String> expandMdmByGoldenResourcePid(IResourcePersistentId theGoldenResourcePid) {
|
public Set<String> expandMdmByGoldenResourcePid(
|
||||||
|
RequestPartitionId theRequestPartitionId, IResourcePersistentId<?> theGoldenResourcePid) {
|
||||||
ourLog.debug("About to expand golden resource with PID {}", theGoldenResourcePid);
|
ourLog.debug("About to expand golden resource with PID {}", theGoldenResourcePid);
|
||||||
List<MdmPidTuple> goldenPidSourcePidTuples = myMdmLinkDao.expandPidsByGoldenResourcePidAndMatchResult(
|
final List<MdmPidTuple<?>> goldenPidSourcePidTuples = myMdmLinkDao.expandPidsByGoldenResourcePidAndMatchResult(
|
||||||
theGoldenResourcePid, MdmMatchResultEnum.MATCH);
|
theGoldenResourcePid, MdmMatchResultEnum.MATCH);
|
||||||
return flattenPidTuplesToSet(theGoldenResourcePid, goldenPidSourcePidTuples);
|
return flattenPidTuplesToSet(theRequestPartitionId, theGoldenResourcePid, goldenPidSourcePidTuples);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> expandMdmByGoldenResourceId(IdDt theId) {
|
public Set<String> expandMdmByGoldenResourceId(RequestPartitionId theRequestPartitionId, IdDt theId) {
|
||||||
ourLog.debug("About to expand golden resource with golden resource id {}", theId);
|
ourLog.debug("About to expand golden resource with golden resource id {}", theId);
|
||||||
IResourcePersistentId pidOrThrowException =
|
IResourcePersistentId<?> pidOrThrowException =
|
||||||
myIdHelperService.getPidOrThrowException(RequestPartitionId.allPartitions(), theId);
|
myIdHelperService.getPidOrThrowException(RequestPartitionId.allPartitions(), theId);
|
||||||
return expandMdmByGoldenResourcePid(pidOrThrowException);
|
return expandMdmByGoldenResourcePid(theRequestPartitionId, pidOrThrowException);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public Set<String> flattenPidTuplesToSet(
|
public Set<String> flattenPidTuplesToSet(
|
||||||
IResourcePersistentId initialPid, List<MdmPidTuple> goldenPidSourcePidTuples) {
|
RequestPartitionId theRequestPartitionId,
|
||||||
Set<IResourcePersistentId> flattenedPids = new HashSet<>();
|
IResourcePersistentId<?> theInitialPid,
|
||||||
goldenPidSourcePidTuples.forEach(tuple -> {
|
List<MdmPidTuple<?>> theGoldenPidSourcePidTuples) {
|
||||||
flattenedPids.add(tuple.getSourcePid());
|
final Set<IResourcePersistentId> flattenedPids = theGoldenPidSourcePidTuples.stream()
|
||||||
flattenedPids.add(tuple.getGoldenPid());
|
.map(tuple -> flattenTuple(theRequestPartitionId, tuple))
|
||||||
});
|
.flatMap(Collection::stream)
|
||||||
Set<String> resourceIds = myIdHelperService.translatePidsToFhirResourceIds(flattenedPids);
|
.collect(Collectors.toUnmodifiableSet());
|
||||||
ourLog.debug("Pid {} has been expanded to [{}]", initialPid, String.join(",", resourceIds));
|
final Set<String> resourceIds = myIdHelperService.translatePidsToFhirResourceIds(flattenedPids);
|
||||||
|
ourLog.debug("Pid {} has been expanded to [{}]", theInitialPid, String.join(",", resourceIds));
|
||||||
return resourceIds;
|
return resourceIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
static Set<IResourcePersistentId> flattenTuple(RequestPartitionId theRequestPartitionId, MdmPidTuple<?> theTuple) {
|
||||||
|
if (theRequestPartitionId.isPartitionCovered(theTuple.getGoldenPartitionId())) {
|
||||||
|
if (theRequestPartitionId.isPartitionCovered(theTuple.getSourcePartitionId())) {
|
||||||
|
return Set.of(theTuple.getSourcePid(), theTuple.getGoldenPid());
|
||||||
|
}
|
||||||
|
return Set.of(theTuple.getGoldenPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theRequestPartitionId.isPartitionCovered(theTuple.getSourcePartitionId())) {
|
||||||
|
return Set.of(theTuple.getSourcePid());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
package ca.uhn.fhir.mdm.svc;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||||
|
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
||||||
|
import ca.uhn.fhir.mdm.dao.IMdmLinkDao;
|
||||||
|
import ca.uhn.fhir.mdm.model.MdmPidTuple;
|
||||||
|
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class MdmLinkExpandSvcTest {
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(MdmLinkExpandSvcTest.class);
|
||||||
|
|
||||||
|
private static final int PARTITION_A = 1;
|
||||||
|
private static final int PARTITION_B = 2;
|
||||||
|
private static final int PARTITION_GOLDEN = 3;
|
||||||
|
private static final JpaPid JPA_PID_PARTITION_A_1 = JpaPid.fromId(123L);
|
||||||
|
private static final JpaPid JPA_PID_PARTITION_B = JpaPid.fromId(456L);
|
||||||
|
private static final JpaPid JPA_PID_PARTITION_A_2 = JpaPid.fromId(789L);
|
||||||
|
private static final JpaPid JPA_PID_PARTITION_DEFAULT = JpaPid.fromId(111L);
|
||||||
|
private static final JpaPid JPA_PID_PARTITION_GOLDEN = JpaPid.fromId(999L);
|
||||||
|
private static final Set<JpaPid> ALL_PIDS = Set.of(JPA_PID_PARTITION_A_1, JPA_PID_PARTITION_B, JPA_PID_PARTITION_A_2, JPA_PID_PARTITION_GOLDEN, JPA_PID_PARTITION_DEFAULT);
|
||||||
|
private static final MdmPidTuple<JpaPid> JPA_PID_MDM_PID_TUPLE_1 = MdmPidTuple.fromGoldenAndSourceAndPartitionIds(JPA_PID_PARTITION_GOLDEN, PARTITION_GOLDEN, JPA_PID_PARTITION_A_1, PARTITION_A);
|
||||||
|
private static final MdmPidTuple<JpaPid> JPA_PID_MDM_PID_TUPLE_2 = MdmPidTuple.fromGoldenAndSourceAndPartitionIds(JPA_PID_PARTITION_GOLDEN, PARTITION_GOLDEN, JPA_PID_PARTITION_B, PARTITION_B);
|
||||||
|
private static final MdmPidTuple<JpaPid> JPA_PID_MDM_PID_TUPLE_3 = MdmPidTuple.fromGoldenAndSourceAndPartitionIds(JPA_PID_PARTITION_GOLDEN, PARTITION_GOLDEN, JPA_PID_PARTITION_A_2, PARTITION_A);
|
||||||
|
private static final MdmPidTuple<JpaPid> JPA_PID_MDM_PID_TUPLE_4 = MdmPidTuple.fromGoldenAndSourceAndPartitionIds(JPA_PID_PARTITION_GOLDEN, PARTITION_GOLDEN, JPA_PID_PARTITION_DEFAULT, null);
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IMdmLinkDao<JpaPid,?> myIMdmLinkDao;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IIdHelperService<?> myIdHelperService;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private MdmLinkExpandSvc mySubject;
|
||||||
|
|
||||||
|
void beforeEachExpand() {
|
||||||
|
final Answer<Set<String>> answer = invocation -> {
|
||||||
|
final Set<IResourcePersistentId<?>> param = invocation.getArgument(0);
|
||||||
|
return param.stream()
|
||||||
|
.filter(JpaPid.class::isInstance)
|
||||||
|
.map(JpaPid.class::cast)
|
||||||
|
.map(pid -> Long.toString(pid.getId()))
|
||||||
|
.collect(Collectors.toUnmodifiableSet());
|
||||||
|
};
|
||||||
|
|
||||||
|
when(myIdHelperService.translatePidsToFhirResourceIds(any()))
|
||||||
|
.thenAnswer(answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> partitionsAndExpectedPids() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(RequestPartitionId.allPartitions(), ALL_PIDS),
|
||||||
|
Arguments.of(RequestPartitionId.defaultPartition(), Collections.singleton(JPA_PID_PARTITION_DEFAULT)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_A)), Set.of(JPA_PID_PARTITION_A_1, JPA_PID_PARTITION_A_2)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_B)), Collections.singleton(JPA_PID_PARTITION_B)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_GOLDEN)), Collections.singleton(JPA_PID_PARTITION_GOLDEN)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_A, PARTITION_B)), Set.of(JPA_PID_PARTITION_A_1, JPA_PID_PARTITION_A_2, JPA_PID_PARTITION_B)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(Arrays.asList(PARTITION_A, PARTITION_B, null)), Set.of(JPA_PID_PARTITION_A_1, JPA_PID_PARTITION_A_2, JPA_PID_PARTITION_B, JPA_PID_PARTITION_DEFAULT)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(Arrays.asList(PARTITION_A, PARTITION_B, PARTITION_GOLDEN, null)), ALL_PIDS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("partitionsAndExpectedPids")
|
||||||
|
void expandMdmBySourceResourcePid(RequestPartitionId theRequestPartitionId, Set<JpaPid> theExpectedJpaPids) {
|
||||||
|
beforeEachExpand();
|
||||||
|
|
||||||
|
final JpaPid jpaPid = JpaPid.fromId(123L);
|
||||||
|
when(myIMdmLinkDao.expandPidsBySourcePidAndMatchResult(jpaPid, MdmMatchResultEnum.MATCH)).thenReturn(List.of(
|
||||||
|
JPA_PID_MDM_PID_TUPLE_1,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_1,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_1,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_1,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_2,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_2,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_2,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_2,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_3,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_3,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_3,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_3,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_3,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_4,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_4,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_4,
|
||||||
|
JPA_PID_MDM_PID_TUPLE_4)
|
||||||
|
);
|
||||||
|
|
||||||
|
final Set<String> resolvedPids = mySubject.expandMdmBySourceResourcePid(theRequestPartitionId, jpaPid);
|
||||||
|
|
||||||
|
assertEquals(toPidStrings(theExpectedJpaPids), resolvedPids, String.format("expected: %s, actual: %s", theExpectedJpaPids, resolvedPids));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("partitionsAndExpectedPids")
|
||||||
|
void expandMdmByGoldenResourcePid(RequestPartitionId theRequestPartitionId, Set<JpaPid> theExpectedJpaPids) {
|
||||||
|
beforeEachExpand();
|
||||||
|
|
||||||
|
when(myIMdmLinkDao.expandPidsByGoldenResourcePidAndMatchResult(any(), any()))
|
||||||
|
.thenReturn(List.of(JPA_PID_MDM_PID_TUPLE_1, JPA_PID_MDM_PID_TUPLE_2, JPA_PID_MDM_PID_TUPLE_3, JPA_PID_MDM_PID_TUPLE_4));
|
||||||
|
final JpaPid jpaPid = JpaPid.fromId(123L);
|
||||||
|
final Set<String> resolvedPids = mySubject.expandMdmByGoldenResourcePid(theRequestPartitionId, jpaPid);
|
||||||
|
|
||||||
|
assertEquals(toPidStrings(theExpectedJpaPids), resolvedPids, String.format("expected: %s, actual: %s", theExpectedJpaPids, resolvedPids));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> partitionsAndTuples() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(RequestPartitionId.allPartitions(), JPA_PID_MDM_PID_TUPLE_1, Set.of(JPA_PID_PARTITION_GOLDEN, JPA_PID_PARTITION_A_1)),
|
||||||
|
Arguments.of(RequestPartitionId.defaultPartition(), JPA_PID_MDM_PID_TUPLE_1, Collections.emptySet()),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_A)), JPA_PID_MDM_PID_TUPLE_1, Collections.singleton(JPA_PID_PARTITION_A_1)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_B)), JPA_PID_MDM_PID_TUPLE_1, Collections.emptySet()),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_GOLDEN)), JPA_PID_MDM_PID_TUPLE_1, Collections.singleton(JPA_PID_PARTITION_GOLDEN)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_A, PARTITION_B)), JPA_PID_MDM_PID_TUPLE_1, Collections.singleton(JPA_PID_PARTITION_A_1)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(Arrays.asList(PARTITION_A, PARTITION_B, null)), JPA_PID_MDM_PID_TUPLE_1, Collections.singleton(JPA_PID_PARTITION_A_1)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(Arrays.asList(PARTITION_A, PARTITION_B, PARTITION_GOLDEN, null)), JPA_PID_MDM_PID_TUPLE_1, Set.of(JPA_PID_PARTITION_GOLDEN, JPA_PID_PARTITION_A_1)),
|
||||||
|
Arguments.of(RequestPartitionId.allPartitions(), JPA_PID_MDM_PID_TUPLE_2, Set.of(JPA_PID_PARTITION_GOLDEN, JPA_PID_PARTITION_B)),
|
||||||
|
Arguments.of(RequestPartitionId.defaultPartition(), JPA_PID_MDM_PID_TUPLE_2, Collections.emptySet()),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_A)), JPA_PID_MDM_PID_TUPLE_2, Collections.emptySet()),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_B)), JPA_PID_MDM_PID_TUPLE_2, Collections.singleton(JPA_PID_PARTITION_B)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_GOLDEN)), JPA_PID_MDM_PID_TUPLE_2, Collections.singleton(JPA_PID_PARTITION_GOLDEN)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_A, PARTITION_B)), JPA_PID_MDM_PID_TUPLE_2, Collections.singleton(JPA_PID_PARTITION_B)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(Arrays.asList(PARTITION_A, PARTITION_B, null)), JPA_PID_MDM_PID_TUPLE_2, Collections.singleton(JPA_PID_PARTITION_B)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(Arrays.asList(PARTITION_A, PARTITION_B, PARTITION_GOLDEN, null)), JPA_PID_MDM_PID_TUPLE_2, Set.of(JPA_PID_PARTITION_GOLDEN, JPA_PID_PARTITION_B)),
|
||||||
|
Arguments.of(RequestPartitionId.allPartitions(), JPA_PID_MDM_PID_TUPLE_3, Set.of(JPA_PID_PARTITION_GOLDEN, JPA_PID_PARTITION_A_2)),
|
||||||
|
Arguments.of(RequestPartitionId.defaultPartition(), JPA_PID_MDM_PID_TUPLE_3, Collections.emptySet()),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_A)), JPA_PID_MDM_PID_TUPLE_3, Collections.singleton(JPA_PID_PARTITION_A_2)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_B)), JPA_PID_MDM_PID_TUPLE_3, Collections.emptySet()),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_GOLDEN)), JPA_PID_MDM_PID_TUPLE_3, Collections.singleton(JPA_PID_PARTITION_GOLDEN)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_A, PARTITION_B)), JPA_PID_MDM_PID_TUPLE_3, Collections.singleton(JPA_PID_PARTITION_A_2)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(Arrays.asList(PARTITION_A, PARTITION_B, null)), JPA_PID_MDM_PID_TUPLE_3, Collections.singleton(JPA_PID_PARTITION_A_2)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(Arrays.asList(PARTITION_A, PARTITION_B, PARTITION_GOLDEN, null)), JPA_PID_MDM_PID_TUPLE_3, Set.of(JPA_PID_PARTITION_GOLDEN, JPA_PID_PARTITION_A_2)),
|
||||||
|
Arguments.of(RequestPartitionId.allPartitions(), JPA_PID_MDM_PID_TUPLE_4, Set.of(JPA_PID_PARTITION_GOLDEN, JPA_PID_PARTITION_DEFAULT)),
|
||||||
|
Arguments.of(RequestPartitionId.defaultPartition(), JPA_PID_MDM_PID_TUPLE_4, Collections.singleton(JPA_PID_PARTITION_DEFAULT)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_A)), JPA_PID_MDM_PID_TUPLE_4, Collections.emptySet()),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_B)), JPA_PID_MDM_PID_TUPLE_4, Collections.emptySet()),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_GOLDEN)), JPA_PID_MDM_PID_TUPLE_4, Collections.singleton(JPA_PID_PARTITION_GOLDEN)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(List.of(PARTITION_A, PARTITION_B)), JPA_PID_MDM_PID_TUPLE_4, Collections.emptySet()),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(Arrays.asList(PARTITION_A, PARTITION_B, null)), JPA_PID_MDM_PID_TUPLE_4, Collections.singleton(JPA_PID_PARTITION_DEFAULT)),
|
||||||
|
Arguments.of(RequestPartitionId.fromPartitionIds(Arrays.asList(PARTITION_A, PARTITION_B, PARTITION_GOLDEN, null)), JPA_PID_MDM_PID_TUPLE_4, Set.of(JPA_PID_PARTITION_DEFAULT, JPA_PID_PARTITION_GOLDEN))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("partitionsAndTuples")
|
||||||
|
void flattenTuple(RequestPartitionId theRequestPartitionId, MdmPidTuple<JpaPid> theTuple, Set<JpaPid> theExpectedResourceIds) {
|
||||||
|
assertEquals(theExpectedResourceIds, MdmLinkExpandSvc.flattenTuple(theRequestPartitionId, theTuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private Set<String> toPidStrings(Set<JpaPid> theTheExpectedJpaPids) {
|
||||||
|
return theTheExpectedJpaPids.stream().map(JpaPid::getId).map(id -> Long.toString(id)).collect(Collectors.toUnmodifiableSet());
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,6 +62,10 @@ public final class PatientEverythingParameters {
|
||||||
"Filter the resources to return only resources matching the given _type filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)")
|
"Filter the resources to return only resources matching the given _type filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)")
|
||||||
private StringAndListParam myTypes;
|
private StringAndListParam myTypes;
|
||||||
|
|
||||||
|
@Description(
|
||||||
|
shortDefinition = "If set to true, trigger an MDM expansion of identifiers corresponding to the resources.")
|
||||||
|
private boolean myMdmExpand = false;
|
||||||
|
|
||||||
public IPrimitiveType<Integer> getCount() {
|
public IPrimitiveType<Integer> getCount() {
|
||||||
return myCount;
|
return myCount;
|
||||||
}
|
}
|
||||||
|
@ -94,6 +98,10 @@ public final class PatientEverythingParameters {
|
||||||
return myTypes;
|
return myTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getMdmExpand() {
|
||||||
|
return myMdmExpand;
|
||||||
|
}
|
||||||
|
|
||||||
public void setCount(IPrimitiveType<Integer> theCount) {
|
public void setCount(IPrimitiveType<Integer> theCount) {
|
||||||
this.myCount = theCount;
|
this.myCount = theCount;
|
||||||
}
|
}
|
||||||
|
@ -125,4 +133,8 @@ public final class PatientEverythingParameters {
|
||||||
public void setTypes(StringAndListParam theTypes) {
|
public void setTypes(StringAndListParam theTypes) {
|
||||||
this.myTypes = theTypes;
|
this.myTypes = theTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMdmExpand(Boolean myMdmExpand) {
|
||||||
|
this.myMdmExpand = myMdmExpand;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue