Skip to content

Commit 439db36

Browse files
authored
Merge pull request #7399 from hapifhir/mergeback-3-rel-8-6
Mergeback 3 rel 8 6
2 parents 15b7de7 + 75a2a6b commit 439db36

File tree

7 files changed

+105
-39
lines changed

7 files changed

+105
-39
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
type: fix
3+
issue: 7387
4+
title: "Fixed an issue causing resources not being found when running `$everything` operation on `Encounter` resource
5+
in `REQUEST_TENANT` partitioning mode."
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
---
22
release-date: "2025-11-20"
3-
codename: "TBD"
3+
codename: "Euphoria"

hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoEncounter.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
2626
import ca.uhn.fhir.rest.api.SortSpec;
2727
import ca.uhn.fhir.rest.api.server.IBundleProvider;
28+
import ca.uhn.fhir.rest.api.server.RequestDetails;
2829
import ca.uhn.fhir.rest.param.DateRangeParam;
2930
import ca.uhn.fhir.rest.param.StringParam;
30-
import jakarta.servlet.http.HttpServletRequest;
3131
import org.hl7.fhir.instance.model.api.IBaseResource;
3232
import org.hl7.fhir.instance.model.api.IIdType;
3333
import org.hl7.fhir.instance.model.api.IPrimitiveType;
@@ -39,12 +39,13 @@ public class JpaResourceDaoEncounter<T extends IBaseResource> extends BaseHapiFh
3939

4040
@Override
4141
public IBundleProvider encounterInstanceEverything(
42-
HttpServletRequest theServletRequest,
42+
RequestDetails theRequest,
4343
IIdType theId,
4444
IPrimitiveType<Integer> theCount,
4545
IPrimitiveType<Integer> theOffset,
4646
DateRangeParam theLastUpdated,
4747
SortSpec theSort) {
48+
4849
SearchParameterMap paramMap = new SearchParameterMap();
4950
if (theCount != null) {
5051
paramMap.setCount(theCount.getValue());
@@ -63,17 +64,21 @@ public IBundleProvider encounterInstanceEverything(
6364
if (theId != null) {
6465
paramMap.add("_id", new StringParam(theId.getIdPart()));
6566
}
66-
IBundleProvider retVal = search(paramMap);
67-
return retVal;
67+
68+
if (!isPagingProviderDatabaseBacked(theRequest)) {
69+
paramMap.setLoadSynchronous(true);
70+
}
71+
72+
return search(paramMap, theRequest);
6873
}
6974

7075
@Override
7176
public IBundleProvider encounterTypeEverything(
72-
HttpServletRequest theServletRequest,
77+
RequestDetails theRequest,
7378
IPrimitiveType<Integer> theCount,
7479
IPrimitiveType<Integer> theOffset,
7580
DateRangeParam theLastUpdated,
7681
SortSpec theSort) {
77-
return encounterInstanceEverything(theServletRequest, null, theCount, theOffset, theLastUpdated, theSort);
82+
return encounterInstanceEverything(theRequest, null, theCount, theOffset, theLastUpdated, theSort);
7883
}
7984
}

hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderEncounter.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import ca.uhn.fhir.rest.api.Constants;
3131
import ca.uhn.fhir.rest.api.SortSpec;
3232
import ca.uhn.fhir.rest.api.server.IBundleProvider;
33+
import ca.uhn.fhir.rest.api.server.RequestDetails;
3334
import ca.uhn.fhir.rest.param.DateRangeParam;
3435
import org.hl7.fhir.instance.model.api.IBaseResource;
3536
import org.hl7.fhir.instance.model.api.IIdType;
@@ -40,17 +41,18 @@ public abstract class BaseJpaResourceProviderEncounter<T extends IBaseResource>
4041
/**
4142
* Encounter/123/$everything
4243
*/
44+
@SuppressWarnings("unused")
4345
@Operation(name = JpaConstants.OPERATION_EVERYTHING, idempotent = true, bundleType = BundleTypeEnum.SEARCHSET)
4446
public IBundleProvider EncounterInstanceEverything(
4547
jakarta.servlet.http.HttpServletRequest theServletRequest,
4648
@IdParam IIdType theId,
4749
@Description(
48-
formalDefinition =
50+
value =
4951
"Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
5052
@OperationParam(name = Constants.PARAM_COUNT, typeName = "unsignedInt")
5153
IPrimitiveType<Integer> theCount,
5254
@Description(
53-
formalDefinition =
55+
value =
5456
"Results from this method are returned across multiple pages. This parameter controls the offset when fetching a page.")
5557
@OperationParam(name = Constants.PARAM_OFFSET, typeName = "unsignedInt")
5658
IPrimitiveType<Integer> theOffset,
@@ -59,13 +61,14 @@ public IBundleProvider EncounterInstanceEverything(
5961
"Only return resources which were last updated as specified by the given range")
6062
@OperationParam(name = Constants.PARAM_LASTUPDATED, min = 0, max = 1)
6163
DateRangeParam theLastUpdated,
62-
@Sort SortSpec theSortSpec) {
64+
@Sort SortSpec theSortSpec,
65+
RequestDetails theRequestDetails) {
6366

6467
startRequest(theServletRequest);
6568
try {
6669
return ((IFhirResourceDaoEncounter<?>) getDao())
6770
.encounterInstanceEverything(
68-
theServletRequest, theId, theCount, theOffset, theLastUpdated, theSortSpec);
71+
theRequestDetails, theId, theCount, theOffset, theLastUpdated, theSortSpec);
6972
} finally {
7073
endRequest(theServletRequest);
7174
}
@@ -74,16 +77,17 @@ public IBundleProvider EncounterInstanceEverything(
7477
/**
7578
* /Encounter/$everything
7679
*/
80+
@SuppressWarnings("unused")
7781
@Operation(name = JpaConstants.OPERATION_EVERYTHING, idempotent = true, bundleType = BundleTypeEnum.SEARCHSET)
7882
public IBundleProvider EncounterTypeEverything(
7983
jakarta.servlet.http.HttpServletRequest theServletRequest,
8084
@Description(
81-
formalDefinition =
85+
value =
8286
"Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
8387
@OperationParam(name = Constants.PARAM_COUNT, typeName = "unsignedInt")
8488
IPrimitiveType<Integer> theCount,
8589
@Description(
86-
formalDefinition =
90+
value =
8791
"Results from this method are returned across multiple pages. This parameter controls the offset when fetching a page.")
8892
@OperationParam(name = Constants.PARAM_OFFSET, typeName = "unsignedInt")
8993
IPrimitiveType<Integer> theOffset,
@@ -92,12 +96,13 @@ public IBundleProvider EncounterTypeEverything(
9296
"Only return resources which were last updated as specified by the given range")
9397
@OperationParam(name = Constants.PARAM_LASTUPDATED, min = 0, max = 1)
9498
DateRangeParam theLastUpdated,
95-
@Sort SortSpec theSortSpec) {
99+
@Sort SortSpec theSortSpec,
100+
RequestDetails theRequestDetails) {
96101

97102
startRequest(theServletRequest);
98103
try {
99104
return ((IFhirResourceDaoEncounter<?>) getDao())
100-
.encounterTypeEverything(theServletRequest, theCount, theOffset, theLastUpdated, theSortSpec);
105+
.encounterTypeEverything(theRequestDetails, theCount, theOffset, theLastUpdated, theSortSpec);
101106
} finally {
102107
endRequest(theServletRequest);
103108
}

hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderEncounterDstu2.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import ca.uhn.fhir.rest.api.Constants;
3232
import ca.uhn.fhir.rest.api.SortSpec;
3333
import ca.uhn.fhir.rest.api.server.IBundleProvider;
34+
import ca.uhn.fhir.rest.api.server.RequestDetails;
3435
import ca.uhn.fhir.rest.param.DateRangeParam;
3536

3637
public abstract class BaseJpaResourceProviderEncounterDstu2 extends BaseJpaResourceProvider<Encounter> {
@@ -43,12 +44,12 @@ public IBundleProvider EncounterInstanceEverything(
4344
jakarta.servlet.http.HttpServletRequest theServletRequest,
4445
@IdParam ca.uhn.fhir.model.primitive.IdDt theId,
4546
@Description(
46-
formalDefinition =
47+
value =
4748
"Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
4849
@OperationParam(name = Constants.PARAM_COUNT)
4950
ca.uhn.fhir.model.primitive.UnsignedIntDt theCount,
5051
@Description(
51-
formalDefinition =
52+
value =
5253
"Results from this method are returned across multiple pages. This parameter controls the offset when fetching a page.")
5354
@OperationParam(name = Constants.PARAM_OFFSET)
5455
ca.uhn.fhir.model.primitive.UnsignedIntDt theOffset,
@@ -57,13 +58,14 @@ public IBundleProvider EncounterInstanceEverything(
5758
"Only return resources which were last updated as specified by the given range")
5859
@OperationParam(name = Constants.PARAM_LASTUPDATED, min = 0, max = 1)
5960
DateRangeParam theLastUpdated,
60-
@Sort SortSpec theSortSpec) {
61+
@Sort SortSpec theSortSpec,
62+
RequestDetails theRequestDetails) {
6163

6264
startRequest(theServletRequest);
6365
try {
6466
return ((IFhirResourceDaoEncounter<Encounter>) getDao())
6567
.encounterInstanceEverything(
66-
theServletRequest, theId, theCount, theOffset, theLastUpdated, theSortSpec);
68+
theRequestDetails, theId, theCount, theOffset, theLastUpdated, theSortSpec);
6769
} finally {
6870
endRequest(theServletRequest);
6971
}
@@ -76,12 +78,12 @@ public IBundleProvider EncounterInstanceEverything(
7678
public IBundleProvider EncounterTypeEverything(
7779
jakarta.servlet.http.HttpServletRequest theServletRequest,
7880
@Description(
79-
formalDefinition =
81+
value =
8082
"Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
8183
@OperationParam(name = Constants.PARAM_COUNT)
8284
ca.uhn.fhir.model.primitive.UnsignedIntDt theCount,
8385
@Description(
84-
formalDefinition =
86+
value =
8587
"Results from this method are returned across multiple pages. This parameter controls the offset when fetching a page.")
8688
@OperationParam(name = Constants.PARAM_OFFSET)
8789
ca.uhn.fhir.model.primitive.UnsignedIntDt theOffset,
@@ -90,12 +92,13 @@ public IBundleProvider EncounterTypeEverything(
9092
"Only return resources which were last updated as specified by the given range")
9193
@OperationParam(name = Constants.PARAM_LASTUPDATED, min = 0, max = 1)
9294
DateRangeParam theLastUpdated,
93-
@Sort SortSpec theSortSpec) {
95+
@Sort SortSpec theSortSpec,
96+
RequestDetails theRequestDetails) {
9497

9598
startRequest(theServletRequest);
9699
try {
97100
return ((IFhirResourceDaoEncounter<Encounter>) getDao())
98-
.encounterTypeEverything(theServletRequest, theCount, theOffset, theLastUpdated, theSortSpec);
101+
.encounterTypeEverything(theRequestDetails, theCount, theOffset, theLastUpdated, theSortSpec);
99102
} finally {
100103
endRequest(theServletRequest);
101104
}

hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/MultitenantServerR4Test.java

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@
7070
import org.mockito.InjectMocks;
7171
import org.mockito.Mock;
7272
import org.springframework.beans.factory.annotation.Autowired;
73-
import org.springframework.boot.test.mock.mockito.SpyBean;
7473
import org.springframework.mock.web.MockHttpServletRequest;
74+
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
7575

7676
import java.io.IOException;
7777
import java.nio.charset.StandardCharsets;
@@ -112,7 +112,7 @@
112112
public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Test implements ITestDataBuilder {
113113
@Captor
114114
private ArgumentCaptor<JpaPid> myMatchUrlCacheValueCaptor;
115-
@SpyBean
115+
@MockitoSpyBean
116116
private MemoryCacheService myMemoryCacheService;
117117

118118
@Autowired
@@ -183,6 +183,54 @@ public void testPartitioningDoesNotReturnDuplicatesOnPatientEverything(int theCo
183183
assertThat(foundIds).hasSize(3);
184184
}
185185

186+
/**
187+
* Tests Encounter/$everything operation with REQUEST_TENANT partitioning
188+
*
189+
* Steps:
190+
* 1. Create Patient in TENANT_A
191+
* 2. Create Encounter in TENANT_A referencing the Patient
192+
* 3. Call Encounter/$everything
193+
* 4. Validate response contains both Encounter and Patient
194+
*/
195+
@Test
196+
public void testEncounterEverything_withRequestTenantPartitioning_shouldReturnEncounterAndPatient() {
197+
// Arrange - Set tenant context to TENANT_A
198+
myTenantClientInterceptor.setTenantId(TENANT_A);
199+
200+
// Create Patient in TENANT_A
201+
IIdType patient = createPatient(withTenant(TENANT_A), withActiveTrue());
202+
203+
// Create Encounter in TENANT_A referencing the Patient
204+
IIdType encounter = createEncounter(withTenant(TENANT_A), withSubject(patient.toUnqualifiedVersionless().toString()));
205+
206+
// Act - Call Encounter/$everything
207+
Bundle everythingBundle = myClient.operation()
208+
.onInstance(encounter.toUnqualifiedVersionless().toString())
209+
.named("$everything")
210+
.withNoParameters(Parameters.class)
211+
.returnResourceType(Bundle.class)
212+
.execute();
213+
214+
// Assert - Should return at least the Encounter and Patient resources
215+
assertThat(everythingBundle).isNotNull();
216+
assertThat(everythingBundle.getEntry())
217+
.as("Encounter/$everything should return at least Encounter and Patient resources")
218+
.isNotEmpty()
219+
.hasSizeGreaterThanOrEqualTo(2);
220+
221+
// Verify Encounter is in the bundle
222+
boolean hasEncounter = everythingBundle.getEntry().stream()
223+
.anyMatch(entry -> entry.getResource() instanceof Encounter
224+
&& entry.getResource().getIdElement().getIdPart().equals(encounter.getIdPart()));
225+
assertThat(hasEncounter).as("Bundle should contain the Encounter").isTrue();
226+
227+
// Verify Patient is in the bundle (referenced by Encounter)
228+
boolean hasPatient = everythingBundle.getEntry().stream()
229+
.anyMatch(entry -> entry.getResource() instanceof Patient
230+
&& entry.getResource().getIdElement().getIdPart().equals(patient.getIdPart()));
231+
assertThat(hasPatient).as("Bundle should contain the referenced Patient").isTrue();
232+
}
233+
186234
@Test
187235
public void testFetchCapabilityStatement() {
188236
myTenantClientInterceptor.setTenantId(TENANT_A);
@@ -202,7 +250,7 @@ public void testCreateAndRead_NamedTenant() {
202250

203251
runInTransaction(() -> {
204252
PartitionEntity partition = myPartitionDao.findForName(TENANT_A).orElseThrow(IllegalStateException::new);
205-
ResourceTable resourceTable = myResourceTableDao.findById(idA.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
253+
ResourceTable resourceTable = myResourceTableDao.findById(JpaPid.fromId(idA.getIdPartAsLong())).orElseThrow(IllegalStateException::new);
206254
assert resourceTable.getPartitionId().getPartitionId() != null;
207255
assertEquals(partition.getId(), resourceTable.getPartitionId().getPartitionId());
208256
});
@@ -240,7 +288,7 @@ public void testCreateAndRead_DefaultTenant() {
240288
createPatient(withTenant(TENANT_B), withActiveFalse());
241289

242290
runInTransaction(() -> {
243-
ResourceTable resourceTable = myResourceTableDao.findById(idA.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
291+
ResourceTable resourceTable = myResourceTableDao.findById(JpaPid.fromId(idA.getIdPartAsLong())).orElseThrow(IllegalStateException::new);
244292
assertNull(resourceTable.getPartitionId().getPartitionId());
245293
});
246294

@@ -460,7 +508,7 @@ public void testCreateAndRead_NonPartitionableResource_DefaultTenant() {
460508
IIdType idA = createResource("NamingSystem", withTenant(JpaConstants.DEFAULT_PARTITION_NAME), withStatus("draft"));
461509

462510
runInTransaction(() -> {
463-
ResourceTable resourceTable = myResourceTableDao.findById(idA.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
511+
ResourceTable resourceTable = myResourceTableDao.findById(JpaPid.fromId(idA.getIdPartAsLong())).orElseThrow(IllegalStateException::new);
464512
assertNull(resourceTable.getPartitionId().getPartitionId());
465513
});
466514

@@ -514,10 +562,10 @@ public void testTransaction() {
514562
IdType idB = new IdType(response.getEntry().get(1).getResponse().getLocation());
515563

516564
runInTransaction(() -> {
517-
ResourceTable resourceTable = myResourceTableDao.findById(idA.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
565+
ResourceTable resourceTable = myResourceTableDao.findById(JpaPid.fromId(idA.getIdPartAsLong())).orElseThrow(IllegalStateException::new);
518566
assert resourceTable.getPartitionId().getPartitionId() != null;
519567
assertEquals(1, resourceTable.getPartitionId().getPartitionId());
520-
resourceTable = myResourceTableDao.findById(idB.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
568+
resourceTable = myResourceTableDao.findById(JpaPid.fromId(idB.getIdPartAsLong())).orElseThrow(IllegalStateException::new);
521569
assert resourceTable.getPartitionId().getPartitionId() != null;
522570
assertEquals(1, resourceTable.getPartitionId().getPartitionId());
523571
});
@@ -702,9 +750,9 @@ public void testDirectDaoAccess_PartitionInRequestDetails_Create() {
702750
IIdType idB = myPatientDao.create((Patient) patientB, requestDetails).getId();
703751

704752
runInTransaction(() -> {
705-
ResourceTable resourceTable = myResourceTableDao.findById(idA.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
753+
ResourceTable resourceTable = myResourceTableDao.findById(JpaPid.fromId(idA.getIdPartAsLong())).orElseThrow(IllegalStateException::new);
706754
assertNull(resourceTable.getPartitionId().getPartitionId());
707-
resourceTable = myResourceTableDao.findById(idB.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
755+
resourceTable = myResourceTableDao.findById(JpaPid.fromId(idB.getIdPartAsLong())).orElseThrow(IllegalStateException::new);
708756
assert resourceTable.getPartitionId().getPartitionId() != null;
709757
assertEquals(2, resourceTable.getPartitionId().getPartitionId());
710758
});
@@ -769,9 +817,9 @@ public void testDirectDaoAccess_PartitionInRequestDetails_Update() {
769817
myPatientDao.update((Patient) patientB, requestDetails);
770818

771819
runInTransaction(() -> {
772-
ResourceTable resourceTable = myResourceTableDao.findById(idA.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
820+
ResourceTable resourceTable = myResourceTableDao.findById(JpaPid.fromId(idA.getIdPartAsLong())).orElseThrow(IllegalStateException::new);
773821
assertNull(resourceTable.getPartitionId().getPartitionId());
774-
resourceTable = myResourceTableDao.findById(idB.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
822+
resourceTable = myResourceTableDao.findById(JpaPid.fromId(idB.getIdPartAsLong())).orElseThrow(IllegalStateException::new);
775823
assert resourceTable.getPartitionId().getPartitionId() != null;
776824
assertEquals(2, resourceTable.getPartitionId().getPartitionId());
777825
});
@@ -833,10 +881,10 @@ public void testDirectDaoAccess_PartitionInRequestDetails_Transaction() {
833881
IdType idB = new IdType(response.getEntry().get(1).getResponse().getLocation());
834882

835883
runInTransaction(() -> {
836-
ResourceTable resourceTable = myResourceTableDao.findById(idA.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
884+
ResourceTable resourceTable = myResourceTableDao.findById(JpaPid.fromId(idA.getIdPartAsLong())).orElseThrow(IllegalStateException::new);
837885
assert resourceTable.getPartitionId().getPartitionId() != null;
838886
assertEquals(1, resourceTable.getPartitionId().getPartitionId());
839-
resourceTable = myResourceTableDao.findById(idB.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
887+
resourceTable = myResourceTableDao.findById(JpaPid.fromId(idB.getIdPartAsLong())).orElseThrow(IllegalStateException::new);
840888
assert resourceTable.getPartitionId().getPartitionId() != null;
841889
assertEquals(1, resourceTable.getPartitionId().getPartitionId());
842890
});

hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoEncounter.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,24 @@
2121

2222
import ca.uhn.fhir.rest.api.SortSpec;
2323
import ca.uhn.fhir.rest.api.server.IBundleProvider;
24+
import ca.uhn.fhir.rest.api.server.RequestDetails;
2425
import ca.uhn.fhir.rest.param.DateRangeParam;
25-
import jakarta.servlet.http.HttpServletRequest;
2626
import org.hl7.fhir.instance.model.api.IBaseResource;
2727
import org.hl7.fhir.instance.model.api.IIdType;
2828
import org.hl7.fhir.instance.model.api.IPrimitiveType;
2929

3030
public interface IFhirResourceDaoEncounter<T extends IBaseResource> extends IFhirResourceDao<T> {
3131

3232
IBundleProvider encounterInstanceEverything(
33-
HttpServletRequest theServletRequest,
33+
RequestDetails theRequest,
3434
IIdType theId,
3535
IPrimitiveType<Integer> theCount,
3636
IPrimitiveType<Integer> theOffset,
3737
DateRangeParam theLastUpdate,
3838
SortSpec theSort);
3939

4040
IBundleProvider encounterTypeEverything(
41-
HttpServletRequest theServletRequest,
41+
RequestDetails theRequest,
4242
IPrimitiveType<Integer> theCount,
4343
IPrimitiveType<Integer> theOffset,
4444
DateRangeParam theLastUpdated,

0 commit comments

Comments
 (0)