Repository: dhis2/dhis2-core
Pull request #20230: fix: use max limit correctly to avoid NPE  [DHIS2-19050][2.41]
Author: login: muilpp
Created: 2025-03-12T12:25:15Z | Merged: 2025-03-14T14:43:11Z

backport of https://github.com/dhis2/dhis2-core/pull/20160


Diff:
diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/HibernateTrackedEntityStore.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/HibernateTrackedEntityStore.java
index a43234d7714b..f803f832f059 100644
--- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/HibernateTrackedEntityStore.java
+++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/HibernateTrackedEntityStore.java
@@ -337,9 +337,7 @@ private String getCountQueryWithMaxTrackedEntityLimit(TrackedEntityQueryParams p
         + getQuerySelect(params)
         + "FROM "
         + getFromSubQuery(params, true, null)
-        + (params.getEnrolledInTrackerProgram().getMaxTeiCountToReturn() > 0
-            ? getLimitClause(params.getEnrolledInTrackerProgram().getMaxTeiCountToReturn() + 1)
-            : "")
+        + (params.getMaxTeLimit() > 0 ? getLimitClause(params.getMaxTeLimit() + 1) : "")
         + " ) tecount";
   }
 
diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapper.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapper.java
index f9f2b03b04e4..75307390a990 100644
--- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapper.java
+++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapper.java
@@ -141,7 +141,7 @@ public TrackedEntityQueryParams map(TrackedEntityOperationParams operationParams
         .setIncludeDeleted(operationParams.isIncludeDeleted())
         .setPotentialDuplicate(operationParams.getPotentialDuplicate());
 
-    validateGlobalSearchParameters(params);
+    validateSearchOutsideCaptureScopeParameters(params);
 
     return params;
   }
@@ -282,19 +282,20 @@ private void validateTrackedEntityAttributeFilters(
     }
   }
 
-  private void validateGlobalSearchParameters(TrackedEntityQueryParams params)
+  private void validateSearchOutsideCaptureScopeParameters(TrackedEntityQueryParams params)
       throws IllegalQueryException {
-    if (!isLocalSearch(params, params.getUser())) {
-
-      if (params.hasFilters()) {
-        List<String> searchableAttributeIds = getSearchableAttributeIds(params);
-        validateSearchableAttributes(params, searchableAttributeIds);
-      }
+    if (isSearchInCaptureScope(params, params.getUser())) {
+      return;
+    }
 
-      int maxTeiLimit = getMaxTeiLimit(params);
-      checkIfMaxTeiLimitIsReached(params, maxTeiLimit);
-      params.setMaxTeLimit(maxTeiLimit);
+    if (params.hasFilters()) {
+      List<String> searchableAttributeIds = getSearchableAttributeIds(params);
+      validateSearchableAttributes(params, searchableAttributeIds);
     }
+
+    int maxTeiLimit = getMaxTeiLimit(params);
+    params.setMaxTeLimit(maxTeiLimit);
+    checkIfMaxTeiLimitIsReached(params, maxTeiLimit);
   }
 
   private List<String> getSearchableAttributeIds(TrackedEntityQueryParams params) {
@@ -363,7 +364,7 @@ private int getMaxTeiLimit(TrackedEntityQueryParams params) {
     return maxTeiLimit;
   }
 
-  private boolean isLocalSearch(TrackedEntityQueryParams params, User user) {
+  private boolean isSearchInCaptureScope(TrackedEntityQueryParams params, User user) {
     if (OrganisationUnitSelectionMode.CAPTURE == params.getOrgUnitMode()) {
       return true;
     }
diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapperTest.java b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapperTest.java
index 4056dfb5eb7d..b4f41bcd5697 100644
--- a/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapperTest.java
+++ b/dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapperTest.java
@@ -533,7 +533,7 @@ void shouldFailWhenGlobalSearchAndNoAttributeSpecified()
   }
 
   @Test
-  void shouldFailWhenGlobalSearchAndMaxTeLimitReached()
+  void validateSearchOutsideCaptureScopeAndProgramMaxTeLimitReached()
       throws ForbiddenException, BadRequestException {
     user.setTeiSearchOrganisationUnits(Set.of(orgUnit1, orgUnit2));
     user.setOrganisationUnits(emptySet());
@@ -559,6 +559,32 @@ void shouldFailWhenGlobalSearchAndMaxTeLimitReached()
     assertEquals("maxteicountreached", IllegalQueryException.getMessage());
   }
 
+  @Test
+  void validateSearchOutsideCaptureScopeAndTrackedEntityTypeMaxTeLimitReached()
+      throws ForbiddenException, BadRequestException {
+    user.setTeiSearchOrganisationUnits(Set.of(orgUnit1, orgUnit2));
+    user.setOrganisationUnits(emptySet());
+
+    trackedEntityType.setMinAttributesRequiredToSearch(0);
+    trackedEntityType.setMaxTeiCountToReturn(1);
+    when(trackedEntityTypeService.getAllTrackedEntityType()).thenReturn(List.of(trackedEntityType));
+    when(paramsValidator.validateTrackedEntityType(trackedEntityType.getUid(), user))
+        .thenReturn(trackedEntityType);
+    when(trackedEntityStore.getTrackedEntityCountWithMaxTrackedEntityLimit(any())).thenReturn(100);
+
+    TrackedEntityOperationParams operationParams =
+        TrackedEntityOperationParams.builder()
+            .orgUnitMode(ACCESSIBLE)
+            .user(user)
+            .trackedEntityTypeUid(TRACKED_ENTITY_TYPE_UID)
+            .build();
+
+    Exception IllegalQueryException =
+        assertThrows(IllegalQueryException.class, () -> mapper.map(operationParams));
+
+    assertEquals("maxteicountreached", IllegalQueryException.getMessage());
+  }
+
   @Test
   void shouldFailWhenUserHasNoAccessToAnyTrackedEntityType() {
     user.setTeiSearchOrganisationUnits(Set.of(orgUnit1, orgUnit2));


Reviews:
user: login: teleivo
body: is https://github.com/dhis2/dhis2-core/blob/9ea7a2cf5f5c306c4c8d594eef8e74d17e428466/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/HibernateTrackedEntityStore.java#L1047 correct? we did change this in master
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T09:26:41Z
user: login: ameenhere
body: Please create a separate jira issue for the systemLimit changes backport and link it with this PR(or the jira issue 19050)
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T13:57:09Z

Review comments:
diff_hunk: @@ -533,7 +533,7 @@ void shouldFailWhenGlobalSearchAndNoAttributeSpecified()
   }
 
   @Test
-  void shouldFailWhenGlobalSearchAndMaxTeLimitReached()
+  void shouldFailWhenGlobalSearchAndProgramMaxTeLimitReached()
path: dhis-2/dhis-services/dhis-service-tracker/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapperTest.java
user: login: teleivo
body: use `oustide` like you did in your implementation
created_at: 2025-03-13T09:19:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -281,19 +281,20 @@ private void validateTrackedEntityAttributeFilters(
     }
   }
 
-  private void validateGlobalSearchParameters(TrackedEntityQueryParams params)
+  private void validateSearchOutsideCaptureScopeParameters(TrackedEntityQueryParams params)
       throws IllegalQueryException {
-    if (!isLocalSearch(params, params.getUser())) {
-
-      if (params.hasFilters()) {
-        List<String> searchableAttributeIds = getSearchableAttributeIds(params);
-        validateSearchableAttributes(params, searchableAttributeIds);
-      }
+    if (isLocalSearch(params, params.getUser())) {
path: dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityOperationParamsMapper.java
user: login: teleivo
body: please rename this as you renamed the `validateGlobalSearchParameters`
created_at: 2025-03-13T09:22:05Z
author_association: CONTRIBUTOR

PR comments:
user: login: muilpp
created_at: 2025-03-14T13:46:38Z
body: > is
> 
> https://github.com/dhis2/dhis2-core/blob/9ea7a2cf5f5c306c4c8d594eef8e74d17e428466/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/HibernateTrackedEntityStore.java#L1047
> correct? we did change this in master

as per a conversation with @ameenhere, fixing the system limit will be done after the hard freeze
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-03-14T14:16:43Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=dhis2_dhis2-core&pullRequest=20230) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=dhis2_dhis2-core&pullRequest=20230&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=dhis2_dhis2-core&pullRequest=20230&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=dhis2_dhis2-core&pullRequest=20230&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=dhis2_dhis2-core&pullRequest=20230&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=dhis2_dhis2-core&pullRequest=20230&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=dhis2_dhis2-core&pullRequest=20230)


author_association: NONE