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;
|
||||
}
|
||||
|
||||
public boolean isPartitionCovered(Integer thePartitionId) {
|
||||
return isAllPartitions() || getPartitionIds().contains(thePartitionId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LocalDate getPartitionDate() {
|
||||
return myPartitionDate;
|
||||
|
|
|
@ -219,6 +219,7 @@ public class Constants {
|
|||
public static final String PARAM_TAGS = "_tags";
|
||||
public static final String PARAM_TEXT = "_text";
|
||||
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_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.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
public class JpaResourceDaoPatient<T extends IBaseResource> extends BaseHapiFhirResourceDao<T>
|
||||
|
@ -67,6 +66,7 @@ public class JpaResourceDaoPatient<T extends IBaseResource> extends BaseHapiFhir
|
|||
StringAndListParam theNarrative,
|
||||
StringAndListParam theFilter,
|
||||
StringAndListParam theTypes,
|
||||
boolean theMdmExpand,
|
||||
RequestDetails theRequest) {
|
||||
SearchParameterMap paramMap = new SearchParameterMap();
|
||||
if (theCount != null) {
|
||||
|
@ -95,11 +95,8 @@ public class JpaResourceDaoPatient<T extends IBaseResource> extends BaseHapiFhir
|
|||
paramMap.setSort(theSort);
|
||||
paramMap.setLastUpdated(theLastUpdated);
|
||||
if (theIds != null) {
|
||||
if (theRequest.getParameters().containsKey("_mdm")) {
|
||||
String[] paramVal = theRequest.getParameters().get("_mdm");
|
||||
if (Arrays.asList(paramVal).contains("true")) {
|
||||
theIds.getValuesAsQueryTokens().forEach(param -> param.setMdmExpand(true));
|
||||
}
|
||||
if (theMdmExpand) {
|
||||
theIds.getValuesAsQueryTokens().forEach(param -> param.setMdmExpand(true));
|
||||
}
|
||||
paramMap.add("_id", theIds);
|
||||
}
|
||||
|
@ -161,6 +158,7 @@ public class JpaResourceDaoPatient<T extends IBaseResource> extends BaseHapiFhir
|
|||
theQueryParams.getNarrative(),
|
||||
theQueryParams.getFilter(),
|
||||
theQueryParams.getTypes(),
|
||||
theQueryParams.getMdmExpand(),
|
||||
theRequestDetails);
|
||||
}
|
||||
|
||||
|
@ -181,6 +179,7 @@ public class JpaResourceDaoPatient<T extends IBaseResource> extends BaseHapiFhir
|
|||
theQueryParams.getNarrative(),
|
||||
theQueryParams.getFilter(),
|
||||
theQueryParams.getTypes(),
|
||||
theQueryParams.getMdmExpand(),
|
||||
theRequestDetails);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,16 +57,22 @@ public interface IMdmLinkJpaRepository
|
|||
nativeQuery = true)
|
||||
void deleteLinksHistoryWithAnyReferenceToPids(@Param("goldenPids") List<Long> theResourcePids);
|
||||
|
||||
@Query("SELECT ml2.myGoldenResourcePid as goldenPid, ml2.mySourcePid as sourcePid FROM MdmLink ml2 "
|
||||
+ "WHERE ml2.myMatchResult=:matchResult "
|
||||
+ "AND ml2.myGoldenResourcePid IN ("
|
||||
+ "SELECT ml.myGoldenResourcePid FROM MdmLink ml "
|
||||
+ "INNER JOIN ResourceLink hrl "
|
||||
+ "ON hrl.myTargetResourcePid=ml.mySourcePid "
|
||||
+ "AND hrl.mySourceResourcePid=:groupPid "
|
||||
+ "AND hrl.mySourcePath='Group.member.entity' "
|
||||
+ "AND hrl.myTargetResourceType='Patient'"
|
||||
+ ")")
|
||||
// TODO: LD: the calling code in JpaBulkExportProcessor doesn't yet leverage the partition IDs, but maybe it
|
||||
// should?
|
||||
@Query(
|
||||
"SELECT lookup_links.myGoldenResourcePid as goldenPid, gld_rt.myPartitionIdValue as goldenPartitionId, lookup_links.mySourcePid as sourcePid, lookup_links.myPartitionIdValue as sourcePartitionId "
|
||||
+ "FROM MdmLink lookup_links "
|
||||
+ "INNER JOIN ResourceTable gld_rt "
|
||||
+ "on lookup_links.myGoldenResourcePid=gld_rt.myId "
|
||||
+ "WHERE lookup_links.myMatchResult=:matchResult "
|
||||
+ "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(
|
||||
@Param("groupPid") Long theGroupPid, @Param("matchResult") MdmMatchResultEnum theMdmMatchResultEnum);
|
||||
|
||||
|
@ -77,15 +83,23 @@ public interface IMdmLinkJpaRepository
|
|||
interface MdmPidTuple {
|
||||
Long getGoldenPid();
|
||||
|
||||
Integer getGoldenPartitionId();
|
||||
|
||||
Long getSourcePid();
|
||||
|
||||
Integer getSourcePartitionId();
|
||||
}
|
||||
|
||||
@Query("SELECT ml.myGoldenResourcePid as goldenPid, ml.mySourcePid as sourcePid " + "FROM MdmLink ml "
|
||||
+ "INNER JOIN MdmLink ml2 "
|
||||
+ "on ml.myGoldenResourcePid=ml2.myGoldenResourcePid "
|
||||
+ "WHERE ml2.mySourcePid=:sourcePid "
|
||||
+ "AND ml2.myMatchResult=:matchResult "
|
||||
+ "AND ml.myMatchResult=:matchResult")
|
||||
@Query(
|
||||
"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 MdmLink gld_link "
|
||||
+ "on lookup_link.myGoldenResourcePid=gld_link.myGoldenResourcePid "
|
||||
+ "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(
|
||||
@Param("sourcePid") Long theSourcePid, @Param("matchResult") MdmMatchResultEnum theMdmMatchResultEnum);
|
||||
|
||||
|
@ -99,7 +113,12 @@ public interface IMdmLinkJpaRepository
|
|||
@Param("matchResult") MdmMatchResultEnum theMdmMatchResultEnumToExclude);
|
||||
|
||||
@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(
|
||||
@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.IIdType;
|
||||
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.stereotype.Service;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
@ -98,6 +100,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
*/
|
||||
@Service
|
||||
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 String RESOURCE_PID = "RESOURCE_PID";
|
||||
|
||||
|
@ -210,9 +213,6 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
Validate.isTrue(!theIds.isEmpty(), "theIds must not be empty");
|
||||
|
||||
Map<String, JpaPid> retVals = new HashMap<>();
|
||||
RequestPartitionId partitionId = myPartitionSettings.isAllowUnqualifiedCrossPartitionReference()
|
||||
? RequestPartitionId.allPartitions()
|
||||
: theRequestPartitionId;
|
||||
for (String id : theIds) {
|
||||
JpaPid retVal;
|
||||
if (!idRequiresForcedId(id)) {
|
||||
|
@ -223,17 +223,18 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
|
|||
// is a forced id
|
||||
// we must resolve!
|
||||
if (myStorageSettings.isDeleteEnabled()) {
|
||||
retVal = resolveResourceIdentity(partitionId, theResourceType, id, theExcludeDeleted)
|
||||
retVal = resolveResourceIdentity(theRequestPartitionId, theResourceType, id, theExcludeDeleted)
|
||||
.getPersistentId();
|
||||
retVals.put(id, retVal);
|
||||
} else {
|
||||
// fetch from cache... adding to cache if not available
|
||||
String key = toForcedIdToPidKey(partitionId, theResourceType, id);
|
||||
String key = toForcedIdToPidKey(theRequestPartitionId, theResourceType, id);
|
||||
retVal = myMemoryCacheService.getThenPutAfterCommit(
|
||||
MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key, t -> {
|
||||
List<IIdType> ids = Collections.singletonList(new IdType(theResourceType, id));
|
||||
// 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()) {
|
||||
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) {
|
||||
return MdmPidTuple.fromGoldenAndSource(
|
||||
JpaPid.fromId(theMdmPidTuple.getGoldenPid()), JpaPid.fromId(theMdmPidTuple.getSourcePid()));
|
||||
return MdmPidTuple.fromGoldenAndSourceAndPartitionIds(
|
||||
JpaPid.fromId(theMdmPidTuple.getGoldenPid()),
|
||||
theMdmPidTuple.getGoldenPartitionId(),
|
||||
JpaPid.fromId(theMdmPidTuple.getSourcePid()),
|
||||
theMdmPidTuple.getSourcePartitionId());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -112,6 +112,11 @@ public abstract class BaseJpaResourceProviderPatient<T extends IBaseResource> ex
|
|||
max = OperationParam.MAX_UNLIMITED,
|
||||
typeName = "string")
|
||||
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,
|
||||
RequestDetails theRequestDetails) {
|
||||
|
||||
|
@ -126,6 +131,7 @@ public abstract class BaseJpaResourceProviderPatient<T extends IBaseResource> ex
|
|||
everythingParams.setNarrative(toStringAndList(theNarrative));
|
||||
everythingParams.setFilter(toStringAndList(theFilter));
|
||||
everythingParams.setTypes(toStringAndList(theTypes));
|
||||
everythingParams.setMdmExpand(resolveNullValue(theMdmExpand));
|
||||
|
||||
return ((IFhirResourceDaoPatient<?>) getDao())
|
||||
.patientInstanceEverything(theServletRequest, theRequestDetails, everythingParams, theId);
|
||||
|
@ -202,6 +208,11 @@ public abstract class BaseJpaResourceProviderPatient<T extends IBaseResource> ex
|
|||
max = OperationParam.MAX_UNLIMITED,
|
||||
typeName = "id")
|
||||
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,
|
||||
RequestDetails theRequestDetails) {
|
||||
|
||||
|
@ -216,6 +227,7 @@ public abstract class BaseJpaResourceProviderPatient<T extends IBaseResource> ex
|
|||
everythingParams.setNarrative(toStringAndList(theNarrative));
|
||||
everythingParams.setFilter(toStringAndList(theFilter));
|
||||
everythingParams.setTypes(toStringAndList(theTypes));
|
||||
everythingParams.setMdmExpand(resolveNullValue(theMdmExpand));
|
||||
|
||||
return ((IFhirResourceDaoPatient<?>) getDao())
|
||||
.patientTypeEverything(
|
||||
|
@ -261,4 +273,8 @@ public abstract class BaseJpaResourceProviderPatient<T extends IBaseResource> ex
|
|||
}
|
||||
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) {
|
||||
return MdmPidTuple.fromGoldenAndSource(JpaPid.fromId(theGoldenId), JpaPid.fromId(theGroupId));
|
||||
return MdmPidTuple.fromGoldenAndSourceAndPartitionIds(JpaPid.fromId(theGoldenId), null, JpaPid.fromId(theGroupId), null);
|
||||
}
|
||||
|
||||
@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.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.Tuple;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import jakarta.persistence.criteria.*;
|
||||
import org.hl7.fhir.r4.hapi.ctx.FhirR4;
|
||||
import jakarta.persistence.criteria.Path;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -22,9 +22,6 @@ import org.mockito.InjectMocks;
|
|||
import org.mockito.Mock;
|
||||
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.Map;
|
||||
|
||||
|
@ -60,15 +57,14 @@ public class IdHelperServiceTest {
|
|||
void setUp() {
|
||||
subject.setDontCheckActiveTransactionForUnitTest(true);
|
||||
|
||||
when(myStorageSettings.isDeleteEnabled()).thenReturn(true);
|
||||
when(myStorageSettings.getResourceClientIdStrategy()).thenReturn(JpaStorageSettings.ClientIdStrategyEnum.ANY);
|
||||
when(myPartitionSettings.isAllowUnqualifiedCrossPartitionReference()).thenReturn(true);
|
||||
when(myStorageSettings.isDeleteEnabled()).thenReturn(true);
|
||||
when(myStorageSettings.getResourceClientIdStrategy()).thenReturn(JpaStorageSettings.ClientIdStrategyEnum.ANY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveResourcePersistentIds() {
|
||||
//prepare params
|
||||
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionName("Partition-A");
|
||||
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionIdAndName(1, "Partition-A");
|
||||
String resourceType = "Patient";
|
||||
Long id = 123L;
|
||||
List<String> ids = List.of(String.valueOf(id));
|
||||
|
@ -77,25 +73,18 @@ public class IdHelperServiceTest {
|
|||
//prepare results
|
||||
Patient expectedPatient = new Patient();
|
||||
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
|
||||
when(myStorageSettings.isDeleteEnabled()).thenReturn(true);
|
||||
when(myResourceTableDao
|
||||
.findAndResolveByForcedIdWithNoType(eq(resourceType), eq(ids), eq(theExcludeDeleted)))
|
||||
.thenReturn(Collections.singletonList(obj));
|
||||
when(myStorageSettings.isDeleteEnabled()).thenReturn(true);
|
||||
|
||||
Map<String, JpaPid> actualIds = subject.resolveResourcePersistentIds(requestPartitionId, resourceType, ids, theExcludeDeleted);
|
||||
|
||||
//verify results
|
||||
assertFalse(actualIds.isEmpty());
|
||||
assertEquals(id, actualIds.get(ids.get(0)).getId());
|
||||
final ResourceNotFoundException resourceNotFoundException = assertThrows(ResourceNotFoundException.class, () -> subject.resolveResourcePersistentIds(requestPartitionId, resourceType, ids, theExcludeDeleted));
|
||||
assertEquals("HAPI-2001: Resource Patient/123 is not known", resourceNotFoundException.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveResourcePersistentIdsDeleteFalse() {
|
||||
//prepare Params
|
||||
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionName("Partition-A");
|
||||
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionIdAndName(1, "Partition-A");
|
||||
Long id = 123L;
|
||||
String resourceType = "Patient";
|
||||
List<String> ids = List.of(String.valueOf(id));
|
||||
|
@ -107,54 +96,21 @@ public class IdHelperServiceTest {
|
|||
expectedPatient.setId(ids.get(0));
|
||||
|
||||
// configure mock behaviour
|
||||
configureCacheBehaviour(forcedId);
|
||||
configureEntityManagerBehaviour(id, resourceType, ids.get(0));
|
||||
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
|
||||
assertFalse(actualIds.isEmpty());
|
||||
assertEquals(id, actualIds.get(ids.get(0)).getId());
|
||||
//verifyResult
|
||||
assertFalse(actualIds.isEmpty());
|
||||
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() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Path<Object> path = mock(Path.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -229,7 +229,6 @@ public class MdmSearchExpandingInterceptorIT extends BaseMdmR4Test {
|
|||
String id = expectedIds.get(0);
|
||||
|
||||
HashMap<String, String[]> queryParameters = new HashMap<>();
|
||||
queryParameters.put("_mdm", new String[]{"true"});
|
||||
|
||||
HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
|
||||
RequestDetails theDetails = Mockito.mock(RequestDetails.class);
|
||||
|
@ -240,9 +239,11 @@ public class MdmSearchExpandingInterceptorIT extends BaseMdmR4Test {
|
|||
// test
|
||||
myStorageSettings.setAllowMdmExpansion(true);
|
||||
IFhirResourceDaoPatient<Patient> dao = (IFhirResourceDaoPatient<Patient>) myPatientDao;
|
||||
final PatientEverythingParameters queryParams = new PatientEverythingParameters();
|
||||
queryParams.setMdmExpand(true);
|
||||
IBundleProvider outcome = dao.patientInstanceEverything(
|
||||
req,
|
||||
theDetails, new PatientEverythingParameters(), new IdDt(id)
|
||||
theDetails, queryParams, new IdDt(id)
|
||||
);
|
||||
|
||||
// verify return results
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*/
|
||||
package ca.uhn.fhir.mdm.api;
|
||||
|
||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -27,15 +28,18 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
|||
import java.util.Set;
|
||||
|
||||
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.Interceptor;
|
||||
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.partition.IRequestPartitionHelperSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.mdm.api.IMdmLinkExpandSvc;
|
||||
import ca.uhn.fhir.mdm.log.Logs;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
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.TokenParam;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
@ -52,6 +56,9 @@ public class MdmSearchExpandingInterceptor {
|
|||
|
||||
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
|
||||
|
||||
@Autowired
|
||||
private IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
|
||||
|
||||
@Autowired
|
||||
private IMdmLinkExpandSvc myMdmLinkExpandSvc;
|
||||
|
||||
|
@ -59,15 +66,20 @@ public class MdmSearchExpandingInterceptor {
|
|||
private JpaStorageSettings myStorageSettings;
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRESEARCH_REGISTERED)
|
||||
public void hook(SearchParameterMap theSearchParameterMap) {
|
||||
public void hook(RequestDetails theRequestDetails, SearchParameterMap theSearchParameterMap) {
|
||||
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()) {
|
||||
String paramName = set.getKey();
|
||||
List<List<IQueryParameterType>> andList = set.getValue();
|
||||
for (List<IQueryParameterType> orList : andList) {
|
||||
// here we will know if it's an _id param or not
|
||||
// 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.
|
||||
*/
|
||||
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> toAdd = new ArrayList<>();
|
||||
for (IQueryParameterType iQueryParameterType : orList) {
|
||||
|
@ -85,13 +98,13 @@ public class MdmSearchExpandingInterceptor {
|
|||
if (refParam.isMdmExpand()) {
|
||||
ourLog.debug("Found a reference parameter to expand: {}", refParam);
|
||||
// First, attempt to expand as a source resource.
|
||||
Set<String> expandedResourceIds =
|
||||
myMdmLinkExpandSvc.expandMdmBySourceResourceId(new IdDt(refParam.getValue()));
|
||||
Set<String> expandedResourceIds = myMdmLinkExpandSvc.expandMdmBySourceResourceId(
|
||||
theRequestPartitionId, new IdDt(refParam.getValue()));
|
||||
|
||||
// If we failed, attempt to expand as a golden resource
|
||||
if (expandedResourceIds.isEmpty()) {
|
||||
expandedResourceIds =
|
||||
myMdmLinkExpandSvc.expandMdmByGoldenResourceId(new IdDt(refParam.getValue()));
|
||||
expandedResourceIds = myMdmLinkExpandSvc.expandMdmByGoldenResourceId(
|
||||
theRequestPartitionId, new IdDt(refParam.getValue()));
|
||||
}
|
||||
|
||||
// Rebuild the search param list.
|
||||
|
@ -105,7 +118,7 @@ public class MdmSearchExpandingInterceptor {
|
|||
}
|
||||
}
|
||||
} 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
|
||||
* ids of linked resources.
|
||||
*
|
||||
* @param theRequestPartitionId
|
||||
* @param theIdParameter
|
||||
* @param theAddList
|
||||
* @param theRemoveList
|
||||
*/
|
||||
private void expandIdParameter(
|
||||
RequestPartitionId theRequestPartitionId,
|
||||
IQueryParameterType theIdParameter,
|
||||
List<IQueryParameterType> theAddList,
|
||||
List<IQueryParameterType> theRemoveList) {
|
||||
|
@ -157,10 +172,10 @@ public class MdmSearchExpandingInterceptor {
|
|||
} else if (mdmExpand) {
|
||||
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()) {
|
||||
expandedResourceIds = myMdmLinkExpandSvc.expandMdmByGoldenResourceId((IdDt) id);
|
||||
expandedResourceIds = myMdmLinkExpandSvc.expandMdmByGoldenResourceId(theRequestPartitionId, (IdDt) id);
|
||||
}
|
||||
|
||||
// Rebuild
|
||||
|
|
|
@ -20,25 +20,83 @@
|
|||
package ca.uhn.fhir.mdm.model;
|
||||
|
||||
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> {
|
||||
private final T myGoldenPid;
|
||||
|
||||
@Nullable
|
||||
private final Integer myGoldenPartitionId;
|
||||
|
||||
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;
|
||||
mySourcePid = theSourcePid;
|
||||
myGoldenPartitionId = theGoldenPartitionId;
|
||||
mySourcePartitionId = theSourcePartitionId;
|
||||
}
|
||||
|
||||
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() {
|
||||
return myGoldenPid;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getGoldenPartitionId() {
|
||||
return myGoldenPartitionId;
|
||||
}
|
||||
|
||||
public T getSourcePid() {
|
||||
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.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
|
@ -61,88 +63,114 @@ public class MdmLinkExpandSvc implements IMdmLinkExpandSvc {
|
|||
* @return A set of strings representing the FHIR IDs of the expanded resources.
|
||||
*/
|
||||
@Override
|
||||
public Set<String> expandMdmBySourceResource(IBaseResource theResource) {
|
||||
public Set<String> expandMdmBySourceResource(RequestPartitionId theRequestPartitionId, IBaseResource 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
|
||||
* MDM-Matched to this resource.
|
||||
* 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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
@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);
|
||||
return expandMdmBySourceResourcePid(
|
||||
theRequestPartitionId,
|
||||
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
|
||||
* MDM-Matched to this resource.
|
||||
* 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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
@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);
|
||||
List<MdmPidTuple> goldenPidSourcePidTuples =
|
||||
final List<MdmPidTuple<?>> goldenPidSourcePidTuples =
|
||||
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
|
||||
* MDM-Matched to this golden resource.
|
||||
*
|
||||
* @param theRequestPartitionId Partition information from the request
|
||||
* @param theGoldenResourcePid The PID of the golden resource to MDM-Expand.
|
||||
* @return A set of strings representing the FHIR ids of the expanded resources.
|
||||
*/
|
||||
@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);
|
||||
List<MdmPidTuple> goldenPidSourcePidTuples = myMdmLinkDao.expandPidsByGoldenResourcePidAndMatchResult(
|
||||
final List<MdmPidTuple<?>> goldenPidSourcePidTuples = myMdmLinkDao.expandPidsByGoldenResourcePidAndMatchResult(
|
||||
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
|
||||
* 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.
|
||||
* @return A set of strings representing the FHIR ids of the expanded resources.
|
||||
*/
|
||||
@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);
|
||||
List<MdmPidTuple> goldenPidSourcePidTuples = myMdmLinkDao.expandPidsByGoldenResourcePidAndMatchResult(
|
||||
final List<MdmPidTuple<?>> goldenPidSourcePidTuples = myMdmLinkDao.expandPidsByGoldenResourcePidAndMatchResult(
|
||||
theGoldenResourcePid, MdmMatchResultEnum.MATCH);
|
||||
return flattenPidTuplesToSet(theGoldenResourcePid, goldenPidSourcePidTuples);
|
||||
return flattenPidTuplesToSet(theRequestPartitionId, theGoldenResourcePid, goldenPidSourcePidTuples);
|
||||
}
|
||||
|
||||
@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);
|
||||
IResourcePersistentId pidOrThrowException =
|
||||
IResourcePersistentId<?> pidOrThrowException =
|
||||
myIdHelperService.getPidOrThrowException(RequestPartitionId.allPartitions(), theId);
|
||||
return expandMdmByGoldenResourcePid(pidOrThrowException);
|
||||
return expandMdmByGoldenResourcePid(theRequestPartitionId, pidOrThrowException);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Set<String> flattenPidTuplesToSet(
|
||||
IResourcePersistentId initialPid, List<MdmPidTuple> goldenPidSourcePidTuples) {
|
||||
Set<IResourcePersistentId> flattenedPids = new HashSet<>();
|
||||
goldenPidSourcePidTuples.forEach(tuple -> {
|
||||
flattenedPids.add(tuple.getSourcePid());
|
||||
flattenedPids.add(tuple.getGoldenPid());
|
||||
});
|
||||
Set<String> resourceIds = myIdHelperService.translatePidsToFhirResourceIds(flattenedPids);
|
||||
ourLog.debug("Pid {} has been expanded to [{}]", initialPid, String.join(",", resourceIds));
|
||||
RequestPartitionId theRequestPartitionId,
|
||||
IResourcePersistentId<?> theInitialPid,
|
||||
List<MdmPidTuple<?>> theGoldenPidSourcePidTuples) {
|
||||
final Set<IResourcePersistentId> flattenedPids = theGoldenPidSourcePidTuples.stream()
|
||||
.map(tuple -> flattenTuple(theRequestPartitionId, tuple))
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
final Set<String> resourceIds = myIdHelperService.translatePidsToFhirResourceIds(flattenedPids);
|
||||
ourLog.debug("Pid {} has been expanded to [{}]", theInitialPid, String.join(",", 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)")
|
||||
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() {
|
||||
return myCount;
|
||||
}
|
||||
|
@ -94,6 +98,10 @@ public final class PatientEverythingParameters {
|
|||
return myTypes;
|
||||
}
|
||||
|
||||
public boolean getMdmExpand() {
|
||||
return myMdmExpand;
|
||||
}
|
||||
|
||||
public void setCount(IPrimitiveType<Integer> theCount) {
|
||||
this.myCount = theCount;
|
||||
}
|
||||
|
@ -125,4 +133,8 @@ public final class PatientEverythingParameters {
|
|||
public void setTypes(StringAndListParam theTypes) {
|
||||
this.myTypes = theTypes;
|
||||
}
|
||||
|
||||
public void setMdmExpand(Boolean myMdmExpand) {
|
||||
this.myMdmExpand = myMdmExpand;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue