From 6f0c23b74e3724bd81242bdfb9589fa17e930352 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 14 Nov 2024 18:09:33 -0500 Subject: [PATCH] Test fixes --- .../fhir/jpa/dao/TransactionProcessor.java | 8 +- .../fhir/jpa/dao/index/IdHelperService.java | 308 +++++++++--------- .../jpa/dao/index/IdHelperServiceTest.java | 202 ++++++++++-- .../jpa/dao/index/IdHelperServiceTest.java | 213 ------------ 4 files changed, 341 insertions(+), 390 deletions(-) delete mode 100644 hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/index/IdHelperServiceTest.java diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java index 5d5922527b4e..88efbb062354 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java @@ -219,8 +219,8 @@ private void preFetchResourcesById( FhirTerser terser = myFhirContext.newTerser(); // Key: The ID of the resource - // Value: True if we should fetch the existing resource details and indexes, - // False if we should only fetch the identity (resource ID and deleted status) + // Value: TRUE if we should prefetch the existing resource details and all stored indexes, + // FALSE if we should prefetch only the identity (resource ID and deleted status) Map idsToPreResolve = new HashMap<>(theEntries.size() * 3); for (IBase nextEntry : theEntries) { @@ -265,10 +265,6 @@ private void preFetchResourcesById( } } - if (idsToPreResolve.isEmpty()) { - return; - } - boolean excludeDeleted = idsToPreResolve.values().stream().anyMatch(t -> !t); Map outcomes = myIdHelperService.resolveResourceIdentities( diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java index 2a48ff6f9b9a..f0ba1f939ed9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java @@ -194,108 +194,20 @@ public Map resolveResourceIdentities( // If we have any pure numeric IDs, and we're not in ANY Client ID mode, then those // numeric IDs can be treated directly as resource PIDs if (myStorageSettings.getResourceClientIdStrategy() != JpaStorageSettings.ClientIdStrategyEnum.ANY) { - List numericIds = null; - for (Iterator iter = ids.iterator(); iter.hasNext(); ) { - IIdType t = iter.next(); - if (isValidPid(t)) { - iter.remove(); - if (numericIds == null) { - numericIds = new ArrayList<>(); - } - MemoryCacheService.TypedPidCacheKey typedResourcePid = new MemoryCacheService.TypedPidCacheKey( - t.getResourceType(), t.getIdPartAsLong(), theRequestPartitionId); - numericIds.add(typedResourcePid); - } - } - if (numericIds != null) { - resolvePureNumericPidsInStandardClientIdMode( - requestPartitionId, numericIds, idToLookup, theExcludeDeleted); - } + resolveResourceIdentitiesForPureNumericIds(requestPartitionId, theExcludeDeleted, ids, idToLookup); } // Do we have any FHIR ID lookups cached for any of the IDs if ((!myStorageSettings.isDeleteEnabled() || !theExcludeDeleted) && !ids.isEmpty()) { - for (Iterator idIterator = ids.iterator(); idIterator.hasNext(); ) { - IIdType nextForcedId = idIterator.next(); - MemoryCacheService.ForcedIdCacheKey nextKey = new MemoryCacheService.ForcedIdCacheKey( - nextForcedId.getResourceType(), nextForcedId.getIdPart(), theRequestPartitionId); - if (!myStorageSettings.isDeleteEnabled() || !theExcludeDeleted) { - List> cachedLookups = myMemoryCacheService.getIfPresent( - MemoryCacheService.CacheEnum.RESOURCE_LOOKUP_BY_FORCED_ID, nextKey); - if (cachedLookups != null && !cachedLookups.isEmpty()) { - idIterator.remove(); - for (IResourceLookup cachedLookup : cachedLookups) { - if (!theExcludeDeleted || cachedLookup.getDeleted() == null) { - idToLookup.put(nextKey.toIdType(myFhirCtx), cachedLookup); - } - } - } - } - } + resolveResourceIdentitiesForFhirIdsUsingCache(requestPartitionId, theExcludeDeleted, ids, idToLookup); } // We still haven't found IDs, let's look them up in the DB if (!ids.isEmpty()) { - CriteriaBuilder cb = myEntityManager.getCriteriaBuilder(); - CriteriaQuery criteriaQuery = cb.createTupleQuery(); - Root from = criteriaQuery.from(ResourceTable.class); - criteriaQuery.multiselect( - from.get("myId"), - from.get("myResourceType"), - from.get("myFhirId"), - from.get("myDeleted"), - from.get("myPartitionIdValue")); - - List outerAndPredicates = new ArrayList<>(2); - if (!theRequestPartitionId.isAllPartitions()) { - getOptionalPartitionPredicate(theRequestPartitionId, cb, from).ifPresent(outerAndPredicates::add); - } - - // one create one clause per id. - List innerIdPredicates = new ArrayList<>(theIds.size()); - for (IIdType next : ids) { - List idPredicates = new ArrayList<>(2); - - if (isNotBlank(next.getResourceType())) { - Predicate typeCriteria = cb.equal(from.get("myResourceType"), next.getResourceType()); - idPredicates.add(typeCriteria); - } - - Predicate idCriteria = cb.equal(from.get("myFhirId"), next.getIdPart()); - idPredicates.add(idCriteria); - - innerIdPredicates.add(cb.and(idPredicates.toArray(EMPTY_PREDICATE_ARRAY))); - } - outerAndPredicates.add(cb.or(innerIdPredicates.toArray(EMPTY_PREDICATE_ARRAY))); - - criteriaQuery.where(cb.and(outerAndPredicates.toArray(EMPTY_PREDICATE_ARRAY))); - TypedQuery query = myEntityManager.createQuery(criteriaQuery); - List results = query.getResultList(); - for (Tuple nextId : results) { - // Check if the nextId has a resource ID. It may have a null resource ID if a commit is still pending. - Long resourcePid = nextId.get(0, Long.class); - String resourceType = nextId.get(1, String.class); - String fhirId = nextId.get(2, String.class); - Date deletedAd = nextId.get(3, Date.class); - Integer partitionId = nextId.get(4, Integer.class); - if (resourcePid != null) { - JpaResourceLookup lookup = new JpaResourceLookup( - resourceType, resourcePid, deletedAd, PartitionablePartitionId.with(partitionId, null)); - - MemoryCacheService.ForcedIdCacheKey nextKey = - new MemoryCacheService.ForcedIdCacheKey(resourceType, fhirId, theRequestPartitionId); - IIdType id = nextKey.toIdType(myFhirCtx); - idToLookup.put(id, lookup); - - List valueToCache = idToLookup.get(id); - myMemoryCacheService.putAfterCommit( - MemoryCacheService.CacheEnum.RESOURCE_LOOKUP_BY_FORCED_ID, nextKey, valueToCache); - } - } + resolveResourceIdentitiesForFhirIdsUsingDatabase(theIds, requestPartitionId, ids, idToLookup); } // Convert the multimap into a simple map - // TODO: once we have incorporated the partition ID into the PID, we can be more nuanced here Map retVal = new HashMap<>(); for (Map.Entry next : idToLookup.entries()) { IResourceLookup previousValue = retVal.put(next.getKey(), next.getValue()); @@ -318,6 +230,164 @@ public Map resolveResourceIdentities( return retVal; } + private void resolveResourceIdentitiesForPureNumericIds( + @Nonnull RequestPartitionId theRequestPartitionId, + boolean theExcludeDeleted, + Collection theIdsToResolve, + ListMultimap theMapToPopulate) { + List numericIds = null; + for (Iterator iter = theIdsToResolve.iterator(); iter.hasNext(); ) { + IIdType t = iter.next(); + if (isValidPid(t)) { + iter.remove(); + if (numericIds == null) { + numericIds = new ArrayList<>(); + } + MemoryCacheService.TypedPidCacheKey typedResourcePid = new MemoryCacheService.TypedPidCacheKey( + t.getResourceType(), t.getIdPartAsLong(), theRequestPartitionId); + numericIds.add(typedResourcePid); + } + } + if (numericIds != null) { + if (!myStorageSettings.isDeleteEnabled() || !theExcludeDeleted) { + for (Iterator forcedIdIterator = numericIds.iterator(); + forcedIdIterator.hasNext(); ) { + MemoryCacheService.TypedPidCacheKey typedResourcePid = forcedIdIterator.next(); + List> cachedLookup = myMemoryCacheService.getIfPresent( + MemoryCacheService.CacheEnum.RESOURCE_LOOKUP_BY_PID, typedResourcePid); + if (cachedLookup != null) { + forcedIdIterator.remove(); + theMapToPopulate.putAll(typedResourcePid.toIdType(myFhirCtx), cachedLookup); + } + } + } + + if (!numericIds.isEmpty()) { + Collection lookup; + List remainingPids = numericIds.stream() + .map(MemoryCacheService.TypedPidCacheKey::getPid) + .collect(Collectors.toList()); + if (theRequestPartitionId.isAllPartitions()) { + lookup = myResourceTableDao.findLookupFieldsByResourcePid(remainingPids); + } else { + if (theRequestPartitionId.isDefaultPartition()) { + lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartitionNull(remainingPids); + } else if (theRequestPartitionId.hasDefaultPartitionId()) { + lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartitionIdsOrNullPartition( + remainingPids, theRequestPartitionId.getPartitionIdsWithoutDefault()); + } else { + lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartitionIds( + remainingPids, theRequestPartitionId.getPartitionIds()); + } + } + lookup.stream() + .map(t -> new JpaResourceLookup( + (String) t[0], + (Long) t[1], + (Date) t[2], + PartitionablePartitionId.with((Integer) t[3], (LocalDate) t[4]))) + .forEach(t -> { + IIdType id = newIdType(t.getResourceType() + "/" + + t.getPersistentId().toString()); + theMapToPopulate.put(id, t); + + MemoryCacheService.TypedPidCacheKey nextKey = new MemoryCacheService.TypedPidCacheKey( + t.getResourceType(), t.getPersistentId().getId(), theRequestPartitionId); + List value = theMapToPopulate.get(id); + myMemoryCacheService.putAfterCommit( + MemoryCacheService.CacheEnum.RESOURCE_LOOKUP_BY_PID, nextKey, value); + }); + } + } + } + + private void resolveResourceIdentitiesForFhirIdsUsingCache( + @Nonnull RequestPartitionId theRequestPartitionId, + boolean theExcludeDeleted, + Collection theIdsToResolve, + ListMultimap theMapToPopulate) { + for (Iterator idIterator = theIdsToResolve.iterator(); idIterator.hasNext(); ) { + IIdType nextForcedId = idIterator.next(); + MemoryCacheService.ForcedIdCacheKey nextKey = new MemoryCacheService.ForcedIdCacheKey( + nextForcedId.getResourceType(), nextForcedId.getIdPart(), theRequestPartitionId); + if (!myStorageSettings.isDeleteEnabled() || !theExcludeDeleted) { + List> cachedLookups = myMemoryCacheService.getIfPresent( + MemoryCacheService.CacheEnum.RESOURCE_LOOKUP_BY_FORCED_ID, nextKey); + if (cachedLookups != null && !cachedLookups.isEmpty()) { + idIterator.remove(); + for (IResourceLookup cachedLookup : cachedLookups) { + if (!theExcludeDeleted || cachedLookup.getDeleted() == null) { + theMapToPopulate.put(nextKey.toIdType(myFhirCtx), cachedLookup); + } + } + } + } + } + } + + private void resolveResourceIdentitiesForFhirIdsUsingDatabase( + Collection theIds, + RequestPartitionId theRequestPartitionId, + Collection theIdsToResolve, + ListMultimap theMapToPopulate) { + CriteriaBuilder cb = myEntityManager.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = cb.createTupleQuery(); + Root from = criteriaQuery.from(ResourceTable.class); + criteriaQuery.multiselect( + from.get("myId"), + from.get("myResourceType"), + from.get("myFhirId"), + from.get("myDeleted"), + from.get("myPartitionIdValue")); + + List outerAndPredicates = new ArrayList<>(2); + if (!theRequestPartitionId.isAllPartitions()) { + getOptionalPartitionPredicate(theRequestPartitionId, cb, from).ifPresent(outerAndPredicates::add); + } + + // one create one clause per id. + List innerIdPredicates = new ArrayList<>(theIds.size()); + for (IIdType next : theIdsToResolve) { + List idPredicates = new ArrayList<>(2); + + if (isNotBlank(next.getResourceType())) { + Predicate typeCriteria = cb.equal(from.get("myResourceType"), next.getResourceType()); + idPredicates.add(typeCriteria); + } + + Predicate idCriteria = cb.equal(from.get("myFhirId"), next.getIdPart()); + idPredicates.add(idCriteria); + + innerIdPredicates.add(cb.and(idPredicates.toArray(EMPTY_PREDICATE_ARRAY))); + } + outerAndPredicates.add(cb.or(innerIdPredicates.toArray(EMPTY_PREDICATE_ARRAY))); + + criteriaQuery.where(cb.and(outerAndPredicates.toArray(EMPTY_PREDICATE_ARRAY))); + TypedQuery query = myEntityManager.createQuery(criteriaQuery); + List results = query.getResultList(); + for (Tuple nextId : results) { + // Check if the nextId has a resource ID. It may have a null resource ID if a commit is still pending. + Long resourcePid = nextId.get(0, Long.class); + String resourceType = nextId.get(1, String.class); + String fhirId = nextId.get(2, String.class); + Date deletedAd = nextId.get(3, Date.class); + Integer partitionId = nextId.get(4, Integer.class); + if (resourcePid != null) { + JpaResourceLookup lookup = new JpaResourceLookup( + resourceType, resourcePid, deletedAd, PartitionablePartitionId.with(partitionId, null)); + + MemoryCacheService.ForcedIdCacheKey nextKey = + new MemoryCacheService.ForcedIdCacheKey(resourceType, fhirId, theRequestPartitionId); + IIdType id = nextKey.toIdType(myFhirCtx); + theMapToPopulate.put(id, lookup); + + List valueToCache = theMapToPopulate.get(id); + myMemoryCacheService.putAfterCommit( + MemoryCacheService.CacheEnum.RESOURCE_LOOKUP_BY_FORCED_ID, nextKey, valueToCache); + } + } + } + /** * Returns a mapping of Id -> IResourcePersistentId. * If any resource is not found, it will throw ResourceNotFound exception (and no map will be returned) @@ -639,62 +709,6 @@ public RequestPartitionId replaceDefault(RequestPartitionId theRequestPartitionI return theRequestPartitionId; } - private void resolvePureNumericPidsInStandardClientIdMode( - @Nonnull RequestPartitionId theRequestPartitionId, - List thePidsToResolve, - ListMultimap theTargetListToPopulate, - boolean theExcludeDeleted) { - if (!myStorageSettings.isDeleteEnabled() || !theExcludeDeleted) { - for (Iterator forcedIdIterator = thePidsToResolve.iterator(); - forcedIdIterator.hasNext(); ) { - MemoryCacheService.TypedPidCacheKey typedResourcePid = forcedIdIterator.next(); - List> cachedLookup = myMemoryCacheService.getIfPresent( - MemoryCacheService.CacheEnum.RESOURCE_LOOKUP_BY_PID, typedResourcePid); - if (cachedLookup != null) { - forcedIdIterator.remove(); - theTargetListToPopulate.putAll(typedResourcePid.toIdType(myFhirCtx), cachedLookup); - } - } - } - - if (!thePidsToResolve.isEmpty()) { - Collection lookup; - List remainingPids = thePidsToResolve.stream() - .map(MemoryCacheService.TypedPidCacheKey::getPid) - .collect(Collectors.toList()); - if (theRequestPartitionId.isAllPartitions()) { - lookup = myResourceTableDao.findLookupFieldsByResourcePid(remainingPids); - } else { - if (theRequestPartitionId.isDefaultPartition()) { - lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartitionNull(remainingPids); - } else if (theRequestPartitionId.hasDefaultPartitionId()) { - lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartitionIdsOrNullPartition( - remainingPids, theRequestPartitionId.getPartitionIdsWithoutDefault()); - } else { - lookup = myResourceTableDao.findLookupFieldsByResourcePidInPartitionIds( - remainingPids, theRequestPartitionId.getPartitionIds()); - } - } - lookup.stream() - .map(t -> new JpaResourceLookup( - (String) t[0], - (Long) t[1], - (Date) t[2], - PartitionablePartitionId.with((Integer) t[3], (LocalDate) t[4]))) - .forEach(t -> { - IIdType id = newIdType( - t.getResourceType() + "/" + t.getPersistentId().toString()); - theTargetListToPopulate.put(id, t); - - MemoryCacheService.TypedPidCacheKey nextKey = new MemoryCacheService.TypedPidCacheKey( - t.getResourceType(), t.getPersistentId().getId(), theRequestPartitionId); - List value = theTargetListToPopulate.get(id); - myMemoryCacheService.putAfterCommit( - MemoryCacheService.CacheEnum.RESOURCE_LOOKUP_BY_PID, nextKey, value); - }); - } - } - @Override public PersistentIdToForcedIdMap translatePidsToForcedIds(Set theResourceIds) { assert myDontCheckActiveTransactionForUnitTest || TransactionSynchronizationManager.isSynchronizationActive(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/index/IdHelperServiceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/index/IdHelperServiceTest.java index a96a38325b3c..e91a5546af00 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/index/IdHelperServiceTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/index/IdHelperServiceTest.java @@ -5,34 +5,47 @@ 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.cross.IResourceLookup; +import ca.uhn.fhir.jpa.model.cross.JpaResourceLookup; import ca.uhn.fhir.jpa.model.dao.JpaPid; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId; 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.criteria.Path; -import jakarta.persistence.criteria.Root; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaQuery; +import org.hibernate.sql.results.internal.TupleImpl; 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.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -40,7 +53,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class IdHelperServiceTest { @@ -60,23 +72,27 @@ public class IdHelperServiceTest { @Mock private MemoryCacheService myMemoryCacheService; - @Mock + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private EntityManager myEntityManager; @Mock private PartitionSettings myPartitionSettings; - @BeforeEach + @Mock + private TypedQuery myTypedQuery; + + @BeforeEach void setUp() { myHelperSvc.setDontCheckActiveTransactionForUnitTest(true); // lenient because some tests require this setup, and others do not lenient().doReturn(true).when(myStorageSettings).isDeleteEnabled(); - lenient().doReturn(JpaStorageSettings.ClientIdStrategyEnum.ANY).when(myStorageSettings).getResourceClientIdStrategy(); } @Test public void testResolveResourcePersistentIds() { + lenient().doReturn(JpaStorageSettings.ClientIdStrategyEnum.ANY).when(myStorageSettings).getResourceClientIdStrategy(); + //prepare params RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionIdAndName(1, "Partition-A"); String resourceType = "Patient"; @@ -97,6 +113,8 @@ public void testResolveResourcePersistentIds() { @Test public void testResolveResourcePersistentIdsDeleteFalse() { + lenient().doReturn(JpaStorageSettings.ClientIdStrategyEnum.ANY).when(myStorageSettings).getResourceClientIdStrategy(); + //prepare Params RequestPartitionId requestPartitionId = RequestPartitionId.fromPartitionIdAndName(1, "Partition-A"); Long id = 123L; @@ -119,21 +137,157 @@ public void testResolveResourcePersistentIdsDeleteFalse() { assertNull(actualIds.get(ids.get(0))); } + @Test + public void resolveResourcePersistentIds_withValidPids_returnsMap() { + lenient().doReturn(JpaStorageSettings.ClientIdStrategyEnum.ALPHANUMERIC).when(myStorageSettings).getResourceClientIdStrategy(); - private Root getMockedFrom() { - @SuppressWarnings("unchecked") - Path path = mock(Path.class); - @SuppressWarnings("unchecked") - Root from = mock(Root.class); - when(from.get(ArgumentMatchers.any())).thenReturn(path); - return from; - } + RequestPartitionId partitionId = RequestPartitionId.allPartitions(); + String resourceType = Patient.class.getSimpleName(); + List patientIdsToResolve = new ArrayList<>(); + patientIdsToResolve.add("123"); + patientIdsToResolve.add("456"); - private List 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); - } + // test + Map idToPid = myHelperSvc.resolveResourcePersistentIds(partitionId, + resourceType, + patientIdsToResolve); + + assertFalse(idToPid.isEmpty()); + for (String pid : patientIdsToResolve) { + assertThat(idToPid).containsKey(pid); + } + } + + @Test + public void resolveResourcePersistentIds_withForcedIdsAndDeleteEnabled_returnsMap() { + lenient().doReturn(JpaStorageSettings.ClientIdStrategyEnum.ALPHANUMERIC).when(myStorageSettings).getResourceClientIdStrategy(); + + RequestPartitionId partitionId = RequestPartitionId.allPartitions(); + String resourceType = Patient.class.getSimpleName(); + List patientIdsToResolve = new ArrayList<>(); + patientIdsToResolve.add("RED"); + patientIdsToResolve.add("BLUE"); + + Object[] redView = new Object[] { + 123L, + "Patient", + "RED", + new Date(), + null + }; + Object[] blueView = new Object[] { + 456L, + "Patient", + "BLUE", + new Date(), + null + }; + + // when + when(myStorageSettings.isDeleteEnabled()) + .thenReturn(true); + when(myEntityManager.createQuery(any(CriteriaQuery.class))).thenReturn(myTypedQuery); + when(myTypedQuery.getResultList()).thenReturn(List.of( + new TupleImpl(null, redView), + new TupleImpl(null, blueView) + )); + + // test + Map map = myHelperSvc.resolveResourcePersistentIds( + partitionId, + resourceType, + patientIdsToResolve); + + assertFalse(map.isEmpty()); + for (String id : patientIdsToResolve) { + assertThat(map).containsKey(id); + } + } + + @Test + public void resolveResourcePersistenIds_withForcedIdAndDeleteDisabled_returnsMap() { + lenient().doReturn(JpaStorageSettings.ClientIdStrategyEnum.ALPHANUMERIC).when(myStorageSettings).getResourceClientIdStrategy(); + + RequestPartitionId partitionId = RequestPartitionId.allPartitions(); + String resourceType = Patient.class.getSimpleName(); + List patientIdsToResolve = new ArrayList<>(); + patientIdsToResolve.add("RED"); + patientIdsToResolve.add("BLUE"); + + JpaPid red = JpaPid.fromId(123L); + JpaPid blue = JpaPid.fromId(456L); + + // we will pretend the lookup value is in the cache + when(myMemoryCacheService.getIfPresent(eq(MemoryCacheService.CacheEnum.RESOURCE_LOOKUP_BY_FORCED_ID), + any())) + .thenReturn(List.of(new JpaResourceLookup("Patient", red.getId(), null, new PartitionablePartitionId()))) + .thenReturn(List.of(new JpaResourceLookup("Patient", blue.getId(), null, new PartitionablePartitionId()))); + + // test + Map map = myHelperSvc.resolveResourcePersistentIds( + partitionId, + resourceType, + patientIdsToResolve + ); + + assertFalse(map.isEmpty()); + for (String id : patientIdsToResolve) { + assertThat(map).containsKey(id); + } + assertThat(map).containsEntry("RED", red); + assertThat(map).containsEntry("BLUE", blue); + } + + @Test + public void testResolveResourceIdentity_defaultFunctionality(){ + lenient().doReturn(JpaStorageSettings.ClientIdStrategyEnum.ALPHANUMERIC).when(myStorageSettings).getResourceClientIdStrategy(); + + RequestPartitionId partitionId = RequestPartitionId.fromPartitionIdAndName(1, "partition"); + String resourceType = "Patient"; + String resourceForcedId = "AAA"; + + Object[] tuple = new Object[] { + 1L, + "Patient", + "AAA", + new Date(), + null + }; + + when(myEntityManager.createQuery(any(CriteriaQuery.class))).thenReturn(myTypedQuery); + when(myTypedQuery.getResultList()).thenReturn(List.of( + new TupleImpl(null, tuple) + )); + + IResourceLookup result = myHelperSvc.resolveResourceIdentity(partitionId, resourceType, resourceForcedId); + assertEquals(tuple[0], result.getPersistentId().getId()); + assertEquals(tuple[1], result.getResourceType()); + assertEquals(tuple[3], result.getDeleted()); + } + + @Test + public void testResolveResourcePersistentIds_mapDefaultFunctionality(){ + lenient().doReturn(JpaStorageSettings.ClientIdStrategyEnum.ALPHANUMERIC).when(myStorageSettings).getResourceClientIdStrategy(); + + RequestPartitionId partitionId = RequestPartitionId.fromPartitionIdAndName(1, "partition"); + String resourceType = "Patient"; + List ids = Arrays.asList("A", "B", "C"); + + JpaPid resourcePersistentId1 = JpaPid.fromId(1L); + JpaPid resourcePersistentId2 = JpaPid.fromId(2L); + JpaPid resourcePersistentId3 = JpaPid.fromId(3L); + when(myMemoryCacheService.getIfPresent(eq(MemoryCacheService.CacheEnum.RESOURCE_LOOKUP_BY_FORCED_ID), + any())) + .thenReturn(List.of(new JpaResourceLookup("Patient", resourcePersistentId1.getId(), null, new PartitionablePartitionId()))) + .thenReturn(List.of(new JpaResourceLookup("Patient", resourcePersistentId2.getId(), null, new PartitionablePartitionId()))) + .thenReturn(List.of(new JpaResourceLookup("Patient", resourcePersistentId3.getId(), null, new PartitionablePartitionId()))); + + Map result = myHelperSvc.resolveResourcePersistentIds(partitionId, resourceType, ids) + .entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue())); + assertThat(result.keySet()).hasSize(3); + assertEquals(1L, result.get("A").getId()); + assertEquals(2L, result.get("B").getId()); + assertEquals(3L, result.get("C").getId()); + } + } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/index/IdHelperServiceTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/index/IdHelperServiceTest.java deleted file mode 100644 index fa0870d4e67a..000000000000 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/index/IdHelperServiceTest.java +++ /dev/null @@ -1,213 +0,0 @@ -package ca.uhn.fhir.jpa.dao.index; - -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.cross.IResourceLookup; -import ca.uhn.fhir.jpa.model.dao.JpaPid; -import ca.uhn.fhir.jpa.util.MemoryCacheService; -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.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class IdHelperServiceTest { - - @Mock - private JpaStorageSettings myStorageSettings; - - @Mock - private IResourceTableDao myResourceTableDao; - - @Mock - private MemoryCacheService myMemoryCacheService; - - @Mock - private PartitionSettings myPartitionSettings; - - @InjectMocks - private IdHelperService myHelperService; - - @BeforeEach - public void beforeEach() { - myHelperService.setDontCheckActiveTransactionForUnitTest(true); - } - - @Test - public void resolveResourcePersistentIds_withValidPids_returnsMap() { - RequestPartitionId partitionId = RequestPartitionId.allPartitions(); - String resourceType = Patient.class.getSimpleName(); - List patientIdsToResolve = new ArrayList<>(); - patientIdsToResolve.add("123"); - patientIdsToResolve.add("456"); - - // test - Map idToPid = myHelperService.resolveResourcePersistentIds(partitionId, - resourceType, - patientIdsToResolve); - - assertFalse(idToPid.isEmpty()); - for (String pid : patientIdsToResolve) { - assertThat(idToPid).containsKey(pid); - } - } - - @Test - public void resolveResourcePersistentIds_withForcedIdsAndDeleteEnabled_returnsMap() { - RequestPartitionId partitionId = RequestPartitionId.allPartitions(); - String resourceType = Patient.class.getSimpleName(); - List patientIdsToResolve = new ArrayList<>(); - patientIdsToResolve.add("RED"); - patientIdsToResolve.add("BLUE"); - - Object[] redView = new Object[] { - "Patient", - 123l, - "RED", - new Date(), - null, - null - }; - Object[] blueView = new Object[] { - "Patient", - 456l, - "BLUE", - new Date(), - null, - null - }; - - // when - when(myStorageSettings.isDeleteEnabled()) - .thenReturn(true); - when(myResourceTableDao.findAndResolveByForcedIdWithNoType(Mockito.anyString(), - Mockito.anyList(), Mockito.anyBoolean())) - .thenReturn(Collections.singletonList(redView)) - .thenReturn(Collections.singletonList(blueView)); - - // test - Map map = myHelperService.resolveResourcePersistentIds( - partitionId, - resourceType, - patientIdsToResolve); - - assertFalse(map.isEmpty()); - for (String id : patientIdsToResolve) { - assertThat(map).containsKey(id); - } - } - - @Test - public void resolveResourcePersistenIds_withForcedIdAndDeleteDisabled_returnsMap() { - RequestPartitionId partitionId = RequestPartitionId.allPartitions(); - String resourceType = Patient.class.getSimpleName(); - List patientIdsToResolve = new ArrayList<>(); - patientIdsToResolve.add("RED"); - patientIdsToResolve.add("BLUE"); - - JpaPid red = JpaPid.fromIdAndVersion(123L, 123L); - JpaPid blue = JpaPid.fromIdAndVersion(456L, 456L); - - // we will pretend the lookup value is in the cache - when(myMemoryCacheService.getThenPutAfterCommit(any(MemoryCacheService.CacheEnum.class), - Mockito.anyString(), - any(Function.class))) - .thenReturn(red) - .thenReturn(blue); - - // test - Map map = myHelperService.resolveResourcePersistentIds( - partitionId, - resourceType, - patientIdsToResolve - ); - - assertFalse(map.isEmpty()); - for (String id : patientIdsToResolve) { - assertThat(map).containsKey(id); - } - assertThat(map).containsEntry("RED", red); - assertThat(map).containsEntry("BLUE", blue); - } - - @Test - public void testResolveResourceIdentity_defaultFunctionality(){ - RequestPartitionId partitionId = RequestPartitionId.fromPartitionIdAndName(1, "partition"); - String resourceType = "Patient"; - String resourceForcedId = "AAA"; - - Object[] forcedIdView = new Object[6]; - forcedIdView[0] = resourceType; - forcedIdView[1] = 1L; - forcedIdView[2] = resourceForcedId; - forcedIdView[3] = null; - forcedIdView[4] = null; - forcedIdView[5] = null; - - Collection testForcedIdViews = new ArrayList<>(); - testForcedIdViews.add(forcedIdView); - when(myResourceTableDao.findAndResolveByForcedIdWithNoTypeInPartition(any(), any(), any(), anyBoolean())).thenReturn(testForcedIdViews); - - IResourceLookup result = myHelperService.resolveResourceIdentity(partitionId, resourceType, resourceForcedId); - assertEquals(forcedIdView[0], result.getResourceType()); - assertEquals(forcedIdView[1], result.getPersistentId().getId()); - assertEquals(forcedIdView[3], result.getDeleted()); - } - - @Test - public void testResolveResourcePersistentIds_mapDefaultFunctionality(){ - RequestPartitionId partitionId = RequestPartitionId.fromPartitionIdAndName(1, "partition"); - String resourceType = "Patient"; - List ids = Arrays.asList("A", "B", "C"); - - JpaPid resourcePersistentId1 = JpaPid.fromId(1L); - JpaPid resourcePersistentId2 = JpaPid.fromId(2L); - JpaPid resourcePersistentId3 = JpaPid.fromId(3L); - when(myMemoryCacheService.getThenPutAfterCommit(any(), any(), any())) - .thenReturn(resourcePersistentId1) - .thenReturn(resourcePersistentId2) - .thenReturn(resourcePersistentId3); - Map result = myHelperService.resolveResourcePersistentIds(partitionId, resourceType, ids) - .entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue())); - assertThat(result.keySet()).hasSize(3); - assertEquals(1L, result.get("A").getId()); - assertEquals(2L, result.get("B").getId()); - assertEquals(3L, result.get("C").getId()); - } - - @Test - public void testResolveResourcePersistentIds_resourcePidDefaultFunctionality(){ - RequestPartitionId partitionId = RequestPartitionId.fromPartitionIdAndName(1, "partition"); - String resourceType = "Patient"; - Long id = 1L; - - JpaPid jpaPid1 = JpaPid.fromId(id); - when(myStorageSettings.getResourceClientIdStrategy()).thenReturn(JpaStorageSettings.ClientIdStrategyEnum.ANY); - when(myMemoryCacheService.getThenPutAfterCommit(any(), any(), any())).thenReturn(jpaPid1); - JpaPid result = myHelperService.resolveResourcePersistentIds(partitionId, resourceType, id.toString()); - assertEquals(id, result.getId()); - } -}