Fixed search for resources for $everything operation with partitions and MDM (#5504)

* Fixed search for resources for $everything operation with partitions and client id mode = ANY

* fixed formatting

* changelog file renamed for tests

* code review fixes: moved generics mocks to class fields, changed size > 0 to empty check

* removed unused fields

* moved fields back to methods, changed generic anys

* changed imports from javax to jakarta

* code review fixes
This commit is contained in:
kateryna-mironova 2023-12-06 16:26:52 -05:00 committed by GitHub
parent 9027138962
commit da9be29047
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 180 additions and 5 deletions

View File

@ -0,0 +1,6 @@
---
type: fix
jira: SMILE-7624
title: "Previously, it was impossible to find all resources from different partitions for $everything operation
with partitioning.cross_partition_reference_mode=ALLOWED_UNQUALIFIED and dao_config.client_id_mode=ANY.
It's fixed now"

View File

@ -210,7 +210,9 @@ 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)) {
@ -221,18 +223,17 @@ public class IdHelperService implements IIdHelperService<JpaPid> {
// is a forced id
// we must resolve!
if (myStorageSettings.isDeleteEnabled()) {
retVal = resolveResourceIdentity(theRequestPartitionId, theResourceType, id, theExcludeDeleted)
retVal = resolveResourceIdentity(partitionId, theResourceType, id, theExcludeDeleted)
.getPersistentId();
retVals.put(id, retVal);
} else {
// fetch from cache... adding to cache if not available
String key = toForcedIdToPidKey(theRequestPartitionId, theResourceType, id);
String key = toForcedIdToPidKey(partitionId, 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(theRequestPartitionId, ids);
List<JpaPid> resolvedIds = resolveResourcePersistentIdsWithCache(partitionId, ids);
if (resolvedIds.isEmpty()) {
throw new ResourceNotFoundException(Msg.code(1100) + ids.get(0));
}

View File

@ -0,0 +1,168 @@
package ca.uhn.fhir.jpa.dao.index;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
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 jakarta.persistence.EntityManager;
import jakarta.persistence.Tuple;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.*;
import org.hl7.fhir.r4.hapi.ctx.FhirR4;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatchers;
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;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
public class IdHelperServiceTest {
@InjectMocks
private final IdHelperService subject = new IdHelperService();
@Mock
protected IResourceTableDao myResourceTableDao;
@Mock
private JpaStorageSettings myStorageSettings;
@Mock
private FhirContext myFhirCtx;
@Mock
private MemoryCacheService myMemoryCacheService;
@Mock
private EntityManager myEntityManager;
@Mock
private PartitionSettings myPartitionSettings;
@BeforeEach
void setUp() {
subject.setDontCheckActiveTransactionForUnitTest(true);
when(myStorageSettings.isDeleteEnabled()).thenReturn(true);
when(myStorageSettings.getResourceClientIdStrategy()).thenReturn(JpaStorageSettings.ClientIdStrategyEnum.ANY);
when(myPartitionSettings.isAllowUnqualifiedCrossPartitionReference()).thenReturn(true);
}
@Test
public void testResolveResourcePersistentIds() {
//prepare params
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionName("Partition-A");
String resourceType = "Patient";
Long id = 123L;
List<String> ids = List.of(String.valueOf(id));
boolean theExcludeDeleted = false;
//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));
Map<String, JpaPid> actualIds = subject.resolveResourcePersistentIds(requestPartitionId, resourceType, ids, theExcludeDeleted);
//verify results
assertFalse(actualIds.isEmpty());
assertEquals(id, actualIds.get(ids.get(0)).getId());
}
@Test
public void testResolveResourcePersistentIdsDeleteFalse() {
//prepare Params
RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionName("Partition-A");
Long id = 123L;
String resourceType = "Patient";
List<String> ids = List.of(String.valueOf(id));
String forcedId = "(all)/" + resourceType + "/" + id;
boolean theExcludeDeleted = false;
//prepare results
Patient expectedPatient = new Patient();
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);
//verifyResult
assertFalse(actualIds.isEmpty());
assertEquals(id, actualIds.get(ids.get(0)).getId());
}
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);
return from;
}
private List<Tuple> getMockedTupleList(Long idNumber, String resourceType, String id) {
Tuple tuple = mock(Tuple.class);
when(tuple.get(eq(0), eq(Long.class))).thenReturn(idNumber);
when(tuple.get(eq(1), eq(String.class))).thenReturn(resourceType);
when(tuple.get(eq(2), eq(String.class))).thenReturn(id);
return List.of(tuple);
}
}