Repository: dhis2/dhis2-core
Pull request #20126: fix: Use correct get single TE for TE export controller [DHIS2-18541]
Author: login: enricocolasante
Created: 2025-02-28T14:11:49Z | Merged: 2025-03-04T12:19:56Z

Moving `getTrackedEntity()` to use the same code as multiple entities has a big impact in a lot of places in the system and it is going to be done in small steps, changing it feature by feature.

***Changes in this PR***

- Use `getNewTrackedEntity` method in `TrackedEntityExportController`. 
- Use `getNewTrackedEntity` method in `getFileResourceMetadata` method.
- Remove now unused `getTrackedEntity(UID, Program, TrackedEntityParams)` method.
- Pass `program` param from `TrackedEntityService` to `EnrollmentService` to only retrieve enrollments from requested program.
- Remove attribute caches in `TrackedEntityAggregate` as they were not invalidated correctly and some e2e tests were failing because of this.

***Challenges in tests***
`TrackedEntityAggregate` is loading data in new threads so they have their own transaction and not committed data are not visible in those threads, that is why we cannot use `@Transactional` annotation when we are importing data and reading in one one transaction.

***Common misconfigurations in tests***
- Create tracked entity and enrollment but do not create an ownership.
- Create a tracker program without a tracked entity type

***Next Steps***
- Move new methods in all remaining places
- Remove old `getTrackedEntity()` methods and rename new ones
- Add `Optional<T> find(UID)` to our services


Diff:
diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/enrollment/DefaultEnrollmentService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/enrollment/DefaultEnrollmentService.java
index 8d0ee986039d..3ccec80b0433 100644
--- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/enrollment/DefaultEnrollmentService.java
+++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/enrollment/DefaultEnrollmentService.java
@@ -182,7 +182,6 @@ private Enrollment getEnrollment(
       boolean includeDeleted,
       @Nonnull UserDetails user) {
     Enrollment result = new Enrollment();
-    result.setId(enrollment.getId());
     result.setUid(enrollment.getUid());
 
     if (enrollment.getTrackedEntity() != null) {
diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventStore.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventStore.java
index 43cb500d5aad..daf0950b2e00 100644
--- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventStore.java
+++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/event/JdbcEventStore.java
@@ -283,7 +283,6 @@ private List<Event> fetchEvents(EventQueryParams queryParams, PageParams pagePar
               event = eventsByUid.get(eventUid);
             } else {
               event = new Event();
-              event.setId(resultSet.getLong(COLUMN_EVENT_ID));
               event.setUid(eventUid);
               eventsByUid.put(eventUid, event);
               dataElementUids.put(eventUid, new HashSet<>());
diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/DefaultTrackedEntityService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/DefaultTrackedEntityService.java
index 39de7430f216..925b4a275a40 100644
--- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/DefaultTrackedEntityService.java
+++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/DefaultTrackedEntityService.java
@@ -27,35 +27,26 @@
  */
 package org.hisp.dhis.tracker.export.trackedentity;
 
-import static org.hisp.dhis.audit.AuditOperationType.READ;
 import static org.hisp.dhis.audit.AuditOperationType.SEARCH;
 import static org.hisp.dhis.user.CurrentUserUtil.getCurrentUserDetails;
 
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 import lombok.RequiredArgsConstructor;
-import org.hisp.dhis.common.IdentifiableObject;
 import org.hisp.dhis.common.UID;
-import org.hisp.dhis.common.collection.CollectionUtils;
 import org.hisp.dhis.feedback.BadRequestException;
 import org.hisp.dhis.feedback.ForbiddenException;
 import org.hisp.dhis.feedback.NotFoundException;
 import org.hisp.dhis.fileresource.FileResource;
 import org.hisp.dhis.fileresource.FileResourceService;
 import org.hisp.dhis.fileresource.ImageFileDimension;
-import org.hisp.dhis.program.Enrollment;
 import org.hisp.dhis.program.Program;
-import org.hisp.dhis.program.ProgramService;
 import org.hisp.dhis.trackedentity.TrackedEntity;
 import org.hisp.dhis.trackedentity.TrackedEntityAttribute;
-import org.hisp.dhis.trackedentity.TrackedEntityAttributeService;
 import org.hisp.dhis.trackedentity.TrackedEntityProgramOwner;
-import org.hisp.dhis.trackedentity.TrackedEntityType;
-import org.hisp.dhis.trackedentity.TrackedEntityTypeStore;
 import org.hisp.dhis.trackedentityattributevalue.TrackedEntityAttributeValue;
 import org.hisp.dhis.tracker.Page;
 import org.hisp.dhis.tracker.PageParams;
@@ -64,8 +55,6 @@
 import org.hisp.dhis.tracker.audit.TrackedEntityAuditService;
 import org.hisp.dhis.tracker.export.FileResourceStream;
 import org.hisp.dhis.tracker.export.OperationsParamsValidator;
-import org.hisp.dhis.tracker.export.enrollment.EnrollmentOperationParams;
-import org.hisp.dhis.tracker.export.enrollment.EnrollmentService;
 import org.hisp.dhis.tracker.export.relationship.RelationshipService;
 import org.hisp.dhis.tracker.export.trackedentity.aggregates.TrackedEntityAggregate;
 import org.hisp.dhis.user.CurrentUserUtil;
@@ -80,20 +69,12 @@ class DefaultTrackedEntityService implements TrackedEntityService {
 
   private final HibernateTrackedEntityStore trackedEntityStore;
 
-  private final TrackedEntityAttributeService trackedEntityAttributeService;
-
-  private final TrackedEntityTypeStore trackedEntityTypeStore;
-
   private final TrackedEntityAuditService trackedEntityAuditService;
 
   private final TrackerAccessManager trackerAccessManager;
 
   private final TrackedEntityAggregate trackedEntityAggregate;
 
-  private final ProgramService programService;
-
-  private final EnrollmentService enrollmentService;
-
   private final RelationshipService relationshipService;
 
   private final FileResourceService fileResourceService;
@@ -105,7 +86,7 @@ class DefaultTrackedEntityService implements TrackedEntityService {
   @Override
   public FileResourceStream getFileResource(
       @Nonnull UID trackedEntity, @Nonnull UID attribute, @CheckForNull UID program)
-      throws NotFoundException {
+      throws NotFoundException, ForbiddenException {
     FileResource fileResource = getFileResourceMetadata(trackedEntity, attribute, program);
     return FileResourceStream.of(fileResourceService, fileResource);
   }
@@ -116,20 +97,25 @@ public FileResourceStream getFileResourceImage(
       @Nonnull UID attribute,
       @CheckForNull UID program,
       ImageFileDimension dimension)
-      throws NotFoundException {
+      throws NotFoundException, ForbiddenException {
     FileResource fileResource = getFileResourceMetadata(trackedEntity, attribute, program);
     return FileResourceStream.ofImage(fileResourceService, fileResource, dimension);
   }
 
   private FileResource getFileResourceMetadata(
       UID trackedEntityUid, UID attributeUid, @CheckForNull UID programUid)
-      throws NotFoundException {
-    TrackedEntity trackedEntity = trackedEntityStore.getByUid(trackedEntityUid.getValue());
-    if (trackedEntity == null || trackedEntity.isDeleted()) {
-      throw new NotFoundException(TrackedEntity.class, trackedEntityUid.getValue());
-    }
-
-    TrackedEntityAttribute attribute = getAttribute(attributeUid, trackedEntity, programUid);
+      throws NotFoundException, ForbiddenException {
+    TrackedEntity trackedEntity =
+        getNewTrackedEntity(
+            trackedEntityUid, programUid, TrackedEntityParams.FALSE.withIncludeAttributes(true));
+
+    TrackedEntityAttribute attribute =
+        trackedEntity.getTrackedEntityAttributeValues().stream()
+            .map(TrackedEntityAttributeValue::getAttribute)
+            .filter(att -> att.getUid().equals(attributeUid.getValue()))
+            .findFirst()
+            .orElseThrow(
+                () -> new NotFoundException(TrackedEntityAttribute.class, attributeUid.getValue()));
     if (!attribute.getValueType().isFile()) {
       throw new NotFoundException(
           "Tracked entity attribute " + attributeUid.getValue() + " is not a file (or image).");
@@ -154,58 +140,6 @@ private FileResource getFileResourceMetadata(
     return fileResourceService.getExistingFileResource(fileResourceUid);
   }
 
-  /**
-   * Tracked entity attributes are fetched from the program if supplied, otherwise from the tracked
-   * entities type. Access is determined through the program sharing and ownership if present. If no
-   * program is supplied, we fall back to tracked entity type ownership and the registering org unit
-   * of the tracked entity.
-   */
-  private TrackedEntityAttribute getAttribute(
-      UID attributeUid, TrackedEntity trackedEntity, @CheckForNull UID programUid)
-      throws NotFoundException {
-    UserDetails currentUser = getCurrentUserDetails();
-
-    if (programUid != null) {
-      Program program = programService.getProgram(programUid.getValue());
-      if (program == null) {
-        throw new NotFoundException(Program.class, programUid.getValue());
-      }
-
-      if (!trackerAccessManager.canRead(currentUser, trackedEntity, program, false).isEmpty()) {
-        throw new NotFoundException(TrackedEntity.class, trackedEntity.getUid());
-      }
-      return getAttribute(attributeUid, program);
-    }
-
-    if (!trackerAccessManager.canRead(currentUser, trackedEntity).isEmpty()) {
-      throw new NotFoundException(TrackedEntity.class, trackedEntity.getUid());
-    }
-    return getAttribute(attributeUid, trackedEntity.getTrackedEntityType());
-  }
-
-  private TrackedEntityAttribute getAttribute(UID attribute, Program program)
-      throws NotFoundException {
-    Set<TrackedEntityAttribute> attributes =
-        trackedEntityAttributeService.getProgramAttributes(program);
-    return getAttribute(attributes, attribute);
-  }
-
-  private TrackedEntityAttribute getAttribute(UID attribute, TrackedEntityType trackedEntityType)
-      throws NotFoundException {
-    Set<TrackedEntityAttribute> attributes =
-        trackedEntityAttributeService.getTrackedEntityTypeAttributes(trackedEntityType);
-    return getAttribute(attributes, attribute);
-  }
-
-  private static TrackedEntityAttribute getAttribute(
-      Set<TrackedEntityAttribute> attributes, UID attribute) throws NotFoundException {
-    return attributes.stream()
-        .filter(att -> attribute.getValue().equals(att.getUid()))
-        .findFirst()
-        .orElseThrow(
-            () -> new NotFoundException(TrackedEntityAttribute.class, attribute.getValue()));
-  }
-
   @Nonnull
   @Override
   public TrackedEntity getNewTrackedEntity(@Nonnull UID uid)
@@ -234,24 +168,6 @@ public TrackedEntity getNewTrackedEntity(
     return getNewTrackedEntity(trackedEntityUid, program, params);
   }
 
-  @Nonnull
-  @Override
-  public TrackedEntity getTrackedEntity(
-      @Nonnull UID trackedEntityUid,
-      @CheckForNull UID programIdentifier,
-      @Nonnull TrackedEntityParams params)
-      throws NotFoundException, ForbiddenException, BadRequestException {
-    Program program = null;
-    if (programIdentifier != null) {
-      program = programService.getProgram(programIdentifier.getValue());
-      if (program == null) {
-        throw new NotFoundException(Program.class, programIdentifier);
-      }
-    }
-
-    return getTrackedEntity(trackedEntityUid, program, params, getCurrentUserDetails());
-  }
-
   private TrackedEntity getNewTrackedEntity(UID uid, Program program, TrackedEntityParams params)
       throws NotFoundException, ForbiddenException {
     Page<TrackedEntity> trackedEntities;
@@ -275,69 +191,6 @@ private TrackedEntity getNewTrackedEntity(UID uid, Program program, TrackedEntit
     return trackedEntities.getItems().get(0);
   }
 
-  /**
-   * Gets a tracked entity based on the program and org unit ownership.
-   *
-   * @return the TE object if found and accessible by the current user
-   * @throws NotFoundException if uid does not exist
-   * @throws ForbiddenException if TE owner is not in user's scope or not enough sharing access
-   */
-  private TrackedEntity getTrackedEntity(
-      UID uid, Program program, TrackedEntityParams params, UserDetails user)
-      throws NotFoundException, ForbiddenException, BadRequestException {
-    TrackedEntity trackedEntity = trackedEntityStore.getByUid(uid.getValue());
-    if (trackedEntity == null || trackedEntity.isDeleted()) {
-      throw new NotFoundException(TrackedEntity.class, uid);
-    }
-
-    trackedEntityAuditService.addTrackedEntityAudit(READ, user.getUsername(), trackedEntity);
-
-    if (program != null) {
-      List<String> errors =
-          trackerAccessManager.canReadProgramAndTrackedEntityType(user, trackedEntity, program);
-      if (!errors.isEmpty()) {
-        throw new ForbiddenException(errors.toString());
-      }
-
-      String error =
-          trackerAccessManager.canAccessProgramOwner(user, trackedEntity, program, false);
-      if (error != null) {
-        throw new ForbiddenException(error);
-      }
-    } else {
-      if (!trackerAccessManager.canRead(user, trackedEntity).isEmpty()) {
-        throw new ForbiddenException(TrackedEntity.class, uid);
-      }
-    }
-
-    if (params.isIncludeEnrollments()) {
-      EnrollmentOperationParams enrollmentOperationParams =
-          mapToEnrollmentParams(uid, program, params);
-      List<Enrollment> enrollments = enrollmentService.getEnrollments(enrollmentOperationParams);
-      trackedEntity.setEnrollments(new HashSet<>(enrollments));
-    }
-    if (params.isIncludeRelationships()) {
-      trackedEntity.setRelationshipItems(
-          relationshipService.getRelationshipItems(
-              TrackerType.TRACKED_ENTITY, UID.of(trackedEntity), false));
-    }
-    if (params.isIncludeProgramOwners()) {
-      trackedEntity.setProgramOwners(getTrackedEntityProgramOwners(trackedEntity, program));
-    }
-    trackedEntity.setTrackedEntityAttributeValues(
-        getTrackedEntityAttributeValues(trackedEntity, program));
-    return trackedEntity;
-  }
-
-  private EnrollmentOperationParams mapToEnrollmentParams(
-      UID trackedEntity, Program program, TrackedEntityParams params) {
-    return EnrollmentOperationParams.builder()
-        .trackedEntity(trackedEntity)
-        .program(program)
-        .enrollmentParams(params.getEnrollmentParams())
-        .build();
-  }
-
   private static Set<TrackedEntityProgramOwner> getTrackedEntityProgramOwners(
       TrackedEntity trackedEntity, Program program) {
     if (program == null) {
@@ -349,32 +202,6 @@ private static Set<TrackedEntityProgramOwner> getTrackedEntityProgramOwners(
         .collect(Collectors.toSet());
   }
 
-  private Set<TrackedEntityAttributeValue> getTrackedEntityAttributeValues(
-      TrackedEntity trackedEntity, Program program) {
-    TrackedEntityType trackedEntityType = trackedEntity.getTrackedEntityType();
-    if (CollectionUtils.isEmpty(trackedEntityType.getTrackedEntityTypeAttributes())) {
-      // the TrackedEntityAggregate does not fetch the TrackedEntityTypeAttributes at the moment
-      // TODO(DHIS2-18541) bypass ACL as our controller test as the user must have access to the TET
-      // if it has access to the TE.
-      trackedEntityType =
-          trackedEntityTypeStore.getByUidNoAcl(trackedEntity.getTrackedEntityType().getUid());
-    }
-
-    Set<String> teas = // tracked entity type attributes
-        trackedEntityType.getTrackedEntityAttributes().stream()
-            .map(IdentifiableObject::getUid)
-            .collect(Collectors.toSet());
-    if (program != null) { // add program tracked entity attributes
-      teas.addAll(
-          program.getTrackedEntityAttributes().stream()
-              .map(IdentifiableObject::getUid)
-              .collect(Collectors.toSet()));
-    }
-    return trackedEntity.getTrackedEntityAttributeValues().stream()
-        .filter(av -> teas.contains(av.getAttribute().getUid()))
-        .collect(Collectors.toSet());
-  }
-
   @Nonnull
   @Override
   public List<TrackedEntity> getTrackedEntities(
@@ -433,9 +260,6 @@ private List<TrackedEntity> getTrackedEntities(
             getTrackedEntityProgramOwners(
                 trackedEntity, queryParams.getEnrolledInTrackerProgram()));
       }
-      trackedEntity.setTrackedEntityAttributeValues(
-          getTrackedEntityAttributeValues(
-              trackedEntity, queryParams.getEnrolledInTrackerProgram()));
     }
     trackedEntityAuditService.addTrackedEntityAudit(SEARCH, user.getUsername(), trackedEntities);
     return trackedEntities;
diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityService.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityService.java
index 13d6a089f4e0..4a71cbf40bc9 100644
--- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityService.java
+++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityService.java
@@ -44,17 +44,17 @@ public interface TrackedEntityService {
 
   /** Get a file for a tracked entities' attribute. */
   FileResourceStream getFileResource(UID trackedEntity, UID attribute, UID program)
-      throws NotFoundException;
+      throws NotFoundException, ForbiddenException;
 
   /** Get an image for a tracked entities' attribute in the given dimension. */
   FileResourceStream getFileResourceImage(
       UID trackedEntity, UID attribute, UID program, ImageFileDimension dimension)
-      throws NotFoundException;
+      throws NotFoundException, ForbiddenException;
 
   /**
    * Get the tracked entity matching given {@code UID} under the privileges of the currently
    * authenticated user. No program attributes are included, only TETAs. Enrollments and
-   * relationships are not included. Use {@link #getTrackedEntity(UID, UID, TrackedEntityParams)}
+   * relationships are not included. Use {@link #getNewTrackedEntity(UID, UID, TrackedEntityParams)}
    * instead to also get the relationships, enrollments and program attributes.
    */
   @Nonnull
@@ -71,19 +71,6 @@ TrackedEntity getNewTrackedEntity(
       @Nonnull UID uid, UID program, @Nonnull TrackedEntityParams params)
       throws NotFoundException, ForbiddenException;
 
-  /**
-   * Get the tracked entity matching given {@code UID} under the privileges of the currently
-   * authenticated user. If {@code program} is defined, program attributes for such program are
-   * included, otherwise only TETAs are included. It will include enrollments, relationships,
-   * attributes and ownerships as defined in {@code params}.
-   *
-   * @deprecated use {@link #getNewTrackedEntity(UID, UID, TrackedEntityParams)} instead.
-   */
-  @Deprecated(forRemoval = true)
-  @Nonnull
-  TrackedEntity getTrackedEntity(@Nonnull UID uid, UID program, @Nonnull TrackedEntityParams params)
-      throws NotFoundException, ForbiddenException, BadRequestException;
-
   /** Get all tracked entities matching given params. */
   @Nonnull
   List<TrackedEntity> getTrackedEntities(TrackedEntityOperationParams operationParams)
diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/EnrollmentAggregate.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/EnrollmentAggregate.java
index b1bc5ef7d518..ac113b9ae4bd 100644
--- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/EnrollmentAggregate.java
+++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/EnrollmentAggregate.java
@@ -71,6 +71,7 @@ Multimap<String, Enrollment> findByTrackedEntityIds(
                     .enrollmentParams(ctx.getParams().getEnrollmentParams())
                     .trackedEntity(UID.of(id.uid()))
                     .includeDeleted(ctx.getQueryParams().isIncludeDeleted())
+                    .program(ctx.getQueryParams().getEnrolledInTrackerProgram())
                     .build();
             try {
               result.putAll(id.uid(), enrollmentService.getEnrollments(params));
diff --git a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/TrackedEntityAggregate.java b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/TrackedEntityAggregate.java
index 490245a6b2e2..644320c7da3f 100644
--- a/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/TrackedEntityAggregate.java
+++ b/dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/aggregates/TrackedEntityAggregate.java
@@ -86,16 +86,10 @@ public class TrackedEntityAggregate {
 
   @Nonnull private final CacheProvider cacheProvider;
 
-  private Cache<Set<TrackedEntityAttribute>> teAttributesCache;
-
-  private Cache<Map<Program, Set<TrackedEntityAttribute>>> programTeAttributesCache;
-
   private Cache<Context> securityCache;
 
   @PostConstruct
   protected void init() {
-    teAttributesCache = cacheProvider.createTeAttributesCache();
-    programTeAttributesCache = cacheProvider.createProgramTeAttributesCache();
     securityCache = cacheProvider.createSecurityCache();
   }
 
@@ -198,19 +192,11 @@ public List<TrackedEntity> find(
                             filterAttributes(
                                 attributes.get(uid),
                                 ownedTeis.get(uid),
-                                teAttributesCache.get(
-                                    "ALL_ATTRIBUTES",
-                                    s ->
-                                        trackedEntityAttributeService
-                                            .getTrackedEntityAttributesByTrackedEntityTypes()),
-                                programTeAttributesCache.get(
-                                    "ATTRIBUTES_BY_PROGRAM",
-                                    s ->
-                                        trackedEntityAttributeService
-                                            .getTrackedEntityAttributesByProgram()),
+                                trackedEntityAttributeService
+                                    .getTrackedEntityAttributesByTrackedEntityTypes(),
+                                trackedEntityAttributeService.getTrackedEntityAttributesByProgram(),
                                 ctx));
-                        te.setEnrollments(
-                            filterEnrollments(enrollments.get(uid), ownedTeis.get(uid), ctx));
+                        te.setEnrollments(new HashSet<>(enrollments.get(uid)));
                         te.setProgramOwners(new HashSet<>(programOwners.get(uid)));
                         return te;
                       })
@@ -220,23 +206,6 @@ public List<TrackedEntity> find(
         .join();
   }
 
-  /** Filter enrollments based on ownership and super user status. */
-  private Set<Enrollment> filterEnrollments(
-      Collection<Enrollment> enrollments, Collection<String> programs, Context ctx) {
-    Set<Enrollment> enrollmentList = new HashSet<>();
-
-    if (enrollments.isEmpty()) {
-      return enrollmentList;
-    }
-
-    enrollmentList.addAll(
-        enrollments.stream()
-            .filter(e -> (programs.contains(e.getProgram().getUid()) || ctx.isSuperUser()))
-            .toList());
-
-    return enrollmentList;
-  }
-
   /** Filter attributes based on queryParams, ownership and superuser status */
   private Set<TrackedEntityAttributeValue> filterAttributes(
       Collection<TrackedEntityAttributeValue> attributes,
@@ -254,13 +223,11 @@ private Set<TrackedEntityAttributeValue> filterAttributes(
             .map(BaseIdentifiableObject::getUid)
             .collect(Collectors.toSet());
 
-    for (Map.Entry<Program, Set<TrackedEntityAttribute>> entry : teaByProgram.entrySet()) {
-      if (programs.contains(entry.getKey().getUid()) || ctx.isSuperUser()) {
-        allowedAttributeUids.addAll(
-            entry.getValue().stream()
-                .map(BaseIdentifiableObject::getUid)
-                .collect(Collectors.toSet()));
-      }
+    if (ctx.getQueryParams().hasEnrolledInTrackerProgram()) {
+      allowedAttributeUids.addAll(
+          teaByProgram.get(ctx.getQueryParams().getEnrolledInTrackerProgram()).stream()
+              .map(BaseIdentifiableObject::getUid)
+              .collect(Collectors.toSet()));
     }
 
     return attributes.stream()
diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackedEntityProgramOwnerServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/acl/TrackedEntityProgramOwnerServiceTest.java
similarity index 96%
rename from dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackedEntityProgramOwnerServiceTest.java
rename to dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/acl/TrackedEntityProgramOwnerServiceTest.java
index 1746c79461d3..4097faf72fc3 100644
--- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackedEntityProgramOwnerServiceTest.java
+++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/acl/TrackedEntityProgramOwnerServiceTest.java
@@ -25,7 +25,7 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-package org.hisp.dhis.trackedentity;
+package org.hisp.dhis.tracker.acl;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -37,7 +37,9 @@
 import org.hisp.dhis.program.Program;
 import org.hisp.dhis.program.ProgramService;
 import org.hisp.dhis.test.integration.PostgresIntegrationTestBase;
-import org.hisp.dhis.tracker.acl.TrackedEntityProgramOwnerService;
+import org.hisp.dhis.trackedentity.TrackedEntity;
+import org.hisp.dhis.trackedentity.TrackedEntityProgramOwner;
+import org.hisp.dhis.trackedentity.TrackedEntityType;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackerAccessManagerTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/acl/TrackerAccessManagerTest.java
similarity index 99%
rename from dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackerAccessManagerTest.java
rename to dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/acl/TrackerAccessManagerTest.java
index 5f8da190ba65..ee32d8eea8c7 100644
--- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackerAccessManagerTest.java
+++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/acl/TrackerAccessManagerTest.java
@@ -25,7 +25,7 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-package org.hisp.dhis.trackedentity;
+package org.hisp.dhis.tracker.acl;
 
 import static org.hisp.dhis.test.utils.Assertions.assertIsEmpty;
 import static org.hisp.dhis.tracker.acl.TrackerOwnershipManager.OWNERSHIP_ACCESS_DENIED;
@@ -61,9 +61,9 @@
 import org.hisp.dhis.program.ProgramType;
 import org.hisp.dhis.security.acl.AccessStringHelper;
 import org.hisp.dhis.test.integration.PostgresIntegrationTestBase;
-import org.hisp.dhis.tracker.acl.TrackedEntityProgramOwnerService;
-import org.hisp.dhis.tracker.acl.TrackerAccessManager;
-import org.hisp.dhis.tracker.acl.TrackerOwnershipManager;
+import org.hisp.dhis.trackedentity.TrackedEntity;
+import org.hisp.dhis.trackedentity.TrackedEntityType;
+import org.hisp.dhis.trackedentity.TrackedEntityTypeService;
 import org.hisp.dhis.tracker.export.trackedentity.TrackedEntityService;
 import org.hisp.dhis.user.User;
 import org.hisp.dhis.user.UserDetails;
diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackedEntityAuditStoreTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/audit/TrackedEntityAuditStoreTest.java
similarity index 97%
rename from dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackedEntityAuditStoreTest.java
rename to dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/audit/TrackedEntityAuditStoreTest.java
index 155483fc4bbc..55e53e866d0b 100644
--- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/trackedentity/TrackedEntityAuditStoreTest.java
+++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/audit/TrackedEntityAuditStoreTest.java
@@ -25,7 +25,7 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-package org.hisp.dhis.trackedentity;
+package org.hisp.dhis.tracker.audit;
 
 import static org.hisp.dhis.test.utils.Assertions.assertContainsOnly;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -35,7 +35,8 @@
 import java.util.List;
 import org.hisp.dhis.audit.AuditOperationType;
 import org.hisp.dhis.test.integration.PostgresIntegrationTestBase;
-import org.hisp.dhis.tracker.audit.TrackedEntityAuditStore;
+import org.hisp.dhis.trackedentity.TrackedEntityAudit;
+import org.hisp.dhis.trackedentity.TrackedEntityAuditQueryParams;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.TestInstance.Lifecycle;
diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityServiceTest.java
index 81e424b6b2f2..6da0bb60baf0 100644
--- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityServiceTest.java
+++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/tracker/export/trackedentity/TrackedEntityServiceTest.java
@@ -612,7 +612,7 @@ void shouldReturnTrackedEntityIncludingAllAttributesEnrollmentsEventsRelationshi
             .build();
 
     TrackedEntity te =
-        trackedEntityService.getTrackedEntity(
+        trackedEntityService.getNewTrackedEntity(
             UID.of(trackedEntityA), UID.of(programA), TrackedEntityParams.TRUE);
     assertEquals(1, te.getEnrollments().size());
     assertEquals(enrollmentA.getUid(), te.getEnrollments().stream().findFirst().get().getUid());
@@ -720,14 +720,14 @@ void shouldTrackedEntityIncludeSpecificOpenProgram()
 
   @Test
   void shouldReturnEnrollmentsFromSpecifiedProgramWhenRequestingSingleTrackedEntity()
-      throws ForbiddenException, NotFoundException, BadRequestException {
+      throws ForbiddenException, NotFoundException {
     Enrollment enrollmentProgramB = createEnrollment(programB, trackedEntityA, orgUnitA);
     manager.save(enrollmentProgramB);
     trackedEntityA.getEnrollments().add(enrollmentProgramB);
     manager.update(trackedEntityA);
 
     TrackedEntity trackedEntity =
-        trackedEntityService.getTrackedEntity(
+        trackedEntityService.getNewTrackedEntity(
             UID.of(trackedEntityA), UID.of(programA), TrackedEntityParams.TRUE);
 
     assertContainsOnly(Set.of(enrollmentA), trackedEntity.getEnrollments(), Enrollment::getUid);
@@ -735,14 +735,14 @@ void shouldReturnEnrollmentsFromSpecifiedProgramWhenRequestingSingleTrackedEntit
 
   @Test
   void shouldReturnAllEnrollmentsWhenRequestingSingleTrackedEntityAndNoProgramSpecified()
-      throws ForbiddenException, NotFoundException, BadRequestException {
+      throws ForbiddenException, NotFoundException {
     Enrollment enrollmentProgramB = createEnrollment(programB, trackedEntityA, orgUnitA);
     manager.save(enrollmentProgramB);
     trackedEntityA.getEnrollments().add(enrollmentProgramB);
     manager.update(trackedEntityA);
 
     TrackedEntity trackedEntity =
-        trackedEntityService.getTrackedEntity(
+        trackedEntityService.getNewTrackedEntity(
             UID.of(trackedEntityA), null, TrackedEntityParams.TRUE);
 
     assertContainsOnly(
@@ -1407,7 +1407,6 @@ void shouldReturnEventMappedCorrectly()
     assertTrue(eventOpt.isPresent());
     Event event = eventOpt.get();
     assertAll(
-        () -> assertEquals(eventA.getId(), event.getId()),
         () -> assertEquals(eventA.getUid(), event.getUid()),
         () -> assertEquals(EventStatus.ACTIVE, event.getStatus()),
         () -> assertEquals(orgUnitA.getUid(), event.getOrganisationUnit().getUid()),
@@ -1929,7 +1928,7 @@ void shouldFailWhenRequestingSingleTEAndProvidedProgramDoesNotExist() {
         assertThrows(
             NotFoundException.class,
             () ->
-                trackedEntityService.getTrackedEntity(
+                trackedEntityService.getNewTrackedEntity(
                     UID.of(trackedEntityA), UID.of(programUid), TrackedEntityParams.TRUE));
     assertEquals(
         String.format("Program with id %s could not be found.", programUid),
@@ -1943,7 +1942,7 @@ void shouldFailWhenRequestingSingleTEAndProvidedTEDoesNotExist() {
         assertThrows(
             NotFoundException.class,
             () ->
-                trackedEntityService.getTrackedEntity(
+                trackedEntityService.getNewTrackedEntity(
                     UID.of(notExistentTE), UID.of(programA), TrackedEntityParams.TRUE));
     assertEquals(
         String.format("TrackedEntity with id %s could not be found.", notExistentTE),
@@ -1963,10 +1962,10 @@ void shouldFailWhenRequestingSingleTEAndNoDataAccessToProvidedProgram() {
         assertThrows(
             ForbiddenException.class,
             () ->
-                trackedEntityService.getTrackedEntity(
+                trackedEntityService.getNewTrackedEntity(
                     UID.of(trackedEntityA), UID.of(inaccessibleProgram), TrackedEntityParams.TRUE));
     assertContains(
-        String.format("User has no data read access to program: %s", inaccessibleProgram.getUid()),
+        String.format("User has no access to program: %s", inaccessibleProgram.getUid()),
         exception.getMessage());
   }
 
@@ -1978,16 +1977,14 @@ void shouldFailWhenRequestingSingleTEAndTETNotAccessible() {
     TrackedEntity trackedEntity = createTrackedEntity(orgUnitA, inaccessibleTrackedEntityType);
     manager.save(trackedEntity);
 
-    ForbiddenException exception =
+    NotFoundException exception =
         assertThrows(
-            ForbiddenException.class,
+            NotFoundException.class,
             () ->
-                trackedEntityService.getTrackedEntity(
+                trackedEntityService.getNewTrackedEntity(
                     UID.of(trackedEntity), UID.of(programA), TrackedEntityParams.TRUE));
     assertContains(
-        String.format(
-            "User has no data read access to tracked entity type: %s",
-            inaccessibleTrackedEntityType.getUid()),
+        String.format("TrackedEntity with id %s could not be found.", trackedEntity.getUid()),
         exception.getMessage());
   }
 
@@ -2032,14 +2029,14 @@ void shouldFailWhenRequestingSingleTEAndNoMetadataAccessToAnyProgram() {
     makeProgramMetadataInaccessible(programC);
 
     injectSecurityContextUser(user);
-    ForbiddenException exception =
+    NotFoundException exception =
         assertThrows(
-            ForbiddenException.class,
+            NotFoundException.class,
             () ->
-                trackedEntityService.getTrackedEntity(
+                trackedEntityService.getNewTrackedEntity(
                     UID.of(trackedEntityA), null, TrackedEntityParams.TRUE));
     assertContains(
-        String.format("User has no access to TrackedEntity:%s", trackedEntityA.getUid()),
+        String.format("TrackedEntity with id %s could not be found.", trackedEntityA.getUid()),
         exception.getMessage());
   }
 
@@ -2053,15 +2050,15 @@ void shouldFailWhenRequestingSingleTEAndOnlyEventProgramAccessible() {
     manager.save(eventProgram, false);
 
     injectSecurityContextUser(user);
-    ForbiddenException exception =
+    NotFoundException exception =
         assertThrows(
-            ForbiddenException.class,
+            NotFoundException.class,
             () ->
-                trackedEntityService.getTrackedEntity(
+                trackedEntityService.getNewTrackedEntity(
                     UID.of(trackedEntityA), null, TrackedEntityParams.TRUE));
 
     assertContains(
-        String.format("User has no access to TrackedEntity:%s", trackedEntityA.getUid()),
+        String.format("TrackedEntity with id %s could not be found.", trackedEntityA.getUid()),
         exception.getMessage());
   }
 
@@ -2078,15 +2075,15 @@ void shouldFailWhenRequestingSingleTEAndTETDoesNotMatchAnyProgram() {
     manager.update(programC);
 
     injectSecurityContextUser(user);
-    ForbiddenException exception =
+    NotFoundException exception =
         assertThrows(
-            ForbiddenException.class,
+            NotFoundException.class,
             () ->
-                trackedEntityService.getTrackedEntity(
+                trackedEntityService.getNewTrackedEntity(
                     UID.of(trackedEntityA), null, TrackedEntityParams.TRUE));
 
     assertContains(
-        String.format("User has no access to TrackedEntity:%s", trackedEntityA.getUid()),
+        String.format("TrackedEntity with id %s could not be found.", trackedEntityA.getUid()),
         exception.getMessage());
   }
 
@@ -2098,20 +2095,20 @@ void shouldFailWhenRequestingSingleTEAndNoDataAccessToAnyProgram() {
     makeProgramMetadataAccessibleOnly(programC);
 
     injectSecurityContextUser(user);
-    ForbiddenException exception =
+    NotFoundException exception =
         assertThrows(
-            ForbiddenException.class,
+            NotFoundException.class,
             () ->
-                trackedEntityService.getTrackedEntity(
+                trackedEntityService.getNewTrackedEntity(
                     UID.of(trackedEntityA), null, TrackedEntityParams.TRUE));
 
     assertContains(
-        String.format("User has no access to TrackedEntity:%s", trackedEntityA.getUid()),
+        String.format("TrackedEntity with id %s could not be found.", trackedEntityA.getUid()),
         exception.getMessage());
   }
 
   @Test
-  void shouldFailWhenRequestingSingleTEAndNoAccessToTET() {
+  void shouldFailWhenRequestingSingleTEAndNoAccessToAnyTET() {
     injectAdminIntoSecurityContext();
     trackedEntityTypeA.setSharing(Sharing.builder().publicAccess("rw------").build());
     manager.update(trackedEntityA);
@@ -2121,19 +2118,17 @@ void shouldFailWhenRequestingSingleTEAndNoAccessToTET() {
         assertThrows(
             ForbiddenException.class,
             () ->
-                trackedEntityService.getTrackedEntity(
+                trackedEntityService.getNewTrackedEntity(
                     UID.of(trackedEntityA), null, TrackedEntityParams.TRUE));
 
-    assertEquals(
-        String.format("User has no access to TrackedEntity:%s", trackedEntityA.getUid()),
-        exception.getMessage());
+    assertEquals("User has no access to any Tracked Entity Type", exception.getMessage());
   }
 
   @Test
   void shouldReturnProgramAttributesWhenSingleTERequestedAndProgramSpecified()
-      throws ForbiddenException, NotFoundException, BadRequestException {
+      throws ForbiddenException, NotFoundException {
     TrackedEntity trackedEntity =
-        trackedEntityService.getTrackedEntity(
+        trackedEntityService.getNewTrackedEntity(
             UID.of(trackedEntityA), UID.of(programA), TrackedEntityParams.TRUE);
 
     assertContainsOnly(
@@ -2144,9 +2139,9 @@ void shouldReturnProgramAttributesWhenSingleTERequestedAndProgramSpecified()
 
   @Test
   void shouldReturnTrackedEntityTypeAttributesOnlyWhenSingleTERequestedAndNoProgramSpecified()
-      throws ForbiddenException, NotFoundException, BadRequestException {
+      throws ForbiddenException, NotFoundException {
     TrackedEntity trackedEntity =
-        trackedEntityService.getTrackedEntity(
+        trackedEntityService.getNewTrackedEntity(
             UID.of(trackedEntityA), null, TrackedEntityParams.TRUE);
 
     assertContainsOnly(
@@ -2158,7 +2153,7 @@ void shouldReturnTrackedEntityTypeAttributesOnlyWhenSingleTERequestedAndNoProgra
   @Test
   void
       shouldFindTrackedEntityWhenCaptureScopeIndependentFromSearchScopeAndCaptureScopeOrgUnitRequested()
-          throws ForbiddenException, NotFoundException, BadRequestException {
+          throws ForbiddenException, NotFoundException {
     injectAdminIntoSecurityContext();
     programA.setAccessLevel(AccessLevel.OPEN);
     manager.update(programA);
@@ -2171,13 +2166,13 @@ void shouldReturnTrackedEntityTypeAttributesOnlyWhenSingleTERequestedAndNoProgra
 
     assertEquals(
         trackedEntityA,
-        trackedEntityService.getTrackedEntity(
+        trackedEntityService.getNewTrackedEntity(
             UID.of(trackedEntityA), UID.of(programA), TrackedEntityParams.TRUE));
   }
 
   @Test
   void shouldFindTrackedEntityWithEventsWhenEventRequestedAndAccessible()
-      throws ForbiddenException, NotFoundException, BadRequestException {
+      throws ForbiddenException, NotFoundException {
     injectAdminIntoSecurityContext();
     User testUser = createAndAddUser(false, "testUser", emptySet(), emptySet(), "F_EXPORT_DATA");
     testUser.setOrganisationUnits(Set.of(orgUnitA));
@@ -2185,7 +2180,7 @@ void shouldFindTrackedEntityWithEventsWhenEventRequestedAndAccessible()
     injectSecurityContext(UserDetails.fromUser(testUser));
 
     TrackedEntity trackedEntity =
-        trackedEntityService.getTrackedEntity(
+        trackedEntityService.getNewTrackedEntity(
             UID.of(trackedEntityA), UID.of(programA), TrackedEntityParams.TRUE);
 
     assertEquals(trackedEntityA.getUid(), trackedEntity.getUid());
@@ -2201,7 +2196,7 @@ void shouldFindTrackedEntityWithEventsWhenEventRequestedAndAccessible()
 
   @Test
   void shouldFindTrackedEntityWithoutEventsWhenEventRequestedButNotAccessible()
-      throws ForbiddenException, NotFoundException, BadRequestException {
+      throws ForbiddenException, NotFoundException {
     injectAdminIntoSecurityContext();
     programStageA1.setSharing(Sharing.builder().publicAccess("--------").build());
     manager.update(programStageA1);
@@ -2211,7 +2206,7 @@ void shouldFindTrackedEntityWithoutEventsWhenEventRequestedButNotAccessible()
     injectSecurityContext(UserDetails.fromUser(testUser));
 
     TrackedEntity trackedEntity =
-        trackedEntityService.getTrackedEntity(
+        trackedEntityService.getNewTrackedEntity(
             UID.of(trackedEntityA), UID.of(programA), TrackedEntityParams.TRUE);
 
     assertEquals(trackedEntityA.getUid(), trackedEntity.getUid());
diff --git a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerTest.java b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerTest.java
index d3edbb057d59..2ed2dd759ccf 100644
--- a/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerTest.java
+++ b/dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerTest.java
@@ -44,6 +44,7 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.IOException;
@@ -53,6 +54,7 @@
 import org.hisp.dhis.common.CodeGenerator;
 import org.hisp.dhis.common.IdentifiableObject;
 import org.hisp.dhis.common.IdentifiableObjectManager;
+import org.hisp.dhis.common.IllegalQueryException;
 import org.hisp.dhis.common.UID;
 import org.hisp.dhis.common.ValueType;
 import org.hisp.dhis.feedback.ConflictException;
@@ -78,10 +80,12 @@
 import org.hisp.dhis.trackedentity.TrackedEntityType;
 import org.hisp.dhis.trackedentity.TrackedEntityTypeAttribute;
 import org.hisp.dhis.trackedentityattributevalue.TrackedEntityAttributeValue;
+import org.hisp.dhis.tracker.acl.TrackedEntityProgramOwnerService;
 import org.hisp.dhis.tracker.imports.TrackerImportParams;
 import org.hisp.dhis.tracker.imports.TrackerImportService;
 import org.hisp.dhis.tracker.imports.TrackerImportStrategy;
 import org.hisp.dhis.tracker.imports.domain.TrackerObjects;
+import org.hisp.dhis.tracker.trackedentityattributevalue.TrackedEntityAttributeValueService;
 import org.hisp.dhis.user.User;
 import org.hisp.dhis.user.UserRole;
 import org.hisp.dhis.user.sharing.UserAccess;
@@ -94,21 +98,20 @@
 import org.hisp.dhis.webapi.controller.tracker.JsonTrackedEntity;
 import org.hisp.dhis.webapi.controller.tracker.TestSetup;
 import org.hisp.dhis.webapi.utils.ContextUtils;
-import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.transaction.annotation.Transactional;
 
-@Transactional
-@TestInstance(TestInstance.Lifecycle.PER_CLASS)
 class TrackedEntitiesExportControllerTest extends PostgresControllerIntegrationTestBase {
   // Used to generate unique chars for creating test objects like TEA, ...
   private static final String UNIQUE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
   @Autowired private TrackerImportService trackerImportService;
 
+  @Autowired private TrackedEntityAttributeValueService trackedEntityAttributeValueService;
+
+  @Autowired private TrackedEntityProgramOwnerService trackedEntityProgramOwnerService;
+
   @Autowired private IdentifiableObjectManager manager;
 
   private TrackerObjects trackerObjects;
@@ -117,7 +120,7 @@ class TrackedEntitiesExportControllerTest extends PostgresControllerIntegrationT
 
   @Autowired private TestSetup testSetup;
 
-  @BeforeAll
+  @BeforeEach
   void setUp() throws IOException {
     testSetup.setUpMetadata();
 
@@ -130,10 +133,6 @@ void setUp() throws IOException {
     manager.clear();
 
     deleteTrackedEntity(UID.of("woitxQbWYNq"));
-  }
-
-  @BeforeEach
-  void setUpUser() {
     switchContextToUser(importUser);
   }
 
@@ -435,7 +434,7 @@ void getTrackedEntityByIdyWithFieldsRelationshipsNoAccessToTrackedEntityType() {
     this.switchContextToUser(user);
 
     GET("/tracker/trackedEntities/{id}?fields=relationships", from.getUid())
-        .error(HttpStatus.FORBIDDEN);
+        .error(HttpStatus.NOT_FOUND);
   }
 
   @Test
@@ -513,8 +512,8 @@ void getTrackedEntityCsvById() {
     assertTrue(response.header("content-disposition").contains("filename=trackedEntity.csv"));
     assertStartsWith(
         """
-trackedEntity,trackedEntityType,createdAt,createdAtClient,updatedAt,updatedAtClient,orgUnit,inactive,deleted,potentialDuplicate,geometry,latitude,longitude,storedBy,createdBy,updatedBy,attrCreatedAt,attrUpdatedAt,attribute,displayName,value,valueType
-""",
+    trackedEntity,trackedEntityType,createdAt,createdAtClient,updatedAt,updatedAtClient,orgUnit,inactive,deleted,potentialDuplicate,geometry,latitude,longitude,storedBy,createdBy,updatedBy,attrCreatedAt,attrUpdatedAt,attribute,displayName,value,valueType
+    """,
         csvResponse);
     // TEAV order is not deterministic
     assertContains(trackedEntityToCsv(te, trackedEntityTypeAttributeValues.get(0)), csvResponse);
@@ -778,15 +777,19 @@ void getAttributeValuesFileByAttributeAndProgramGivenTrackedEntityTypeAttribute(
 
     this.switchContextToUser(user);
 
-    assertStartsWith(
-        "TrackedEntityAttribute with id " + tetTea.getUid(),
+    HttpResponse response =
         GET(
-                "/tracker/trackedEntities/{trackedEntityUid}/attributes/{attributeUid}/file?program={programUid}",
-                trackedEntity.getUid(),
-                tetTea.getUid(),
-                program.getUid())
-            .error(HttpStatus.NOT_FOUND)
-            .getMessage());
+            "/tracker/trackedEntities/{trackedEntityUid}/attributes/{attributeUid}/file?program={programUid}",
+            trackedEntity.getUid(),
+            tetTea.getUid(),
+            program.getUid());
+
+    assertEquals(HttpStatus.OK, response.status());
+    assertEquals("\"" + file1.getUid() + "\"", response.header("Etag"));
+    assertEquals("no-cache, private", response.header("Cache-Control"));
+    assertEquals(Long.toString(file1.getContentLength()), response.header("Content-Length"));
+    assertEquals("filename=" + file1.getName(), response.header("Content-Disposition"));
+    assertEquals("file content", response.content("text/plain"));
   }
 
   @Test
@@ -834,7 +837,7 @@ void getAttributeValuesFileByAttributeAndProgramIfAttributeIsNotFound() throws C
     addProgramAttributeValue(trackedEntity, program, ValueType.FILE_RESOURCE, file.getUid());
 
     String attributeUid = CodeGenerator.generateUid();
-
+    this.switchContextToUser(user);
     assertStartsWith(
         "TrackedEntityAttribute with id " + attributeUid,
         GET(
@@ -881,6 +884,7 @@ void getAttributeValuesFileByAttributeAndProgramIfAttributeIsNotAFile() {
 
     TrackedEntityAttribute tea =
         addProgramAttributeValue(trackedEntity, program, ValueType.BOOLEAN, "true");
+    this.switchContextToUser(user);
 
     assertStartsWith(
         "Tracked entity attribute " + tea.getUid() + " is not a file",
@@ -903,7 +907,7 @@ void getAttributeValuesFileByAttributeAndProgramIfProgramDoesNotExist() throws C
         addProgramAttributeValue(trackedEntity, program, ValueType.FILE_RESOURCE, file.getUid());
 
     String programUid = CodeGenerator.generateUid();
-
+    this.switchContextToUser(user);
     assertStartsWith(
         "Program",
         GET(
@@ -934,13 +938,13 @@ void getAttributeValuesFileByAttributeAndProgramIfProgramDoesNotExist() throws C
     this.switchContextToUser(user);
 
     assertStartsWith(
-        "TrackedEntity ",
+        "User is not authorized to read data from selected program's tracked entity type",
         GET(
                 "/tracker/trackedEntities/{trackedEntityUid}/attributes/{attributeUid}/file?program={programUid}",
                 trackedEntity.getUid(),
                 tea.getUid(),
                 program.getUid())
-            .error(HttpStatus.NOT_FOUND)
+            .error(HttpStatus.FORBIDDEN)
             .getMessage());
   }
 
@@ -978,9 +982,9 @@ void getAttributeValuesFileByAttributeAndProgramIfNoAttributeValueExists() {
     TrackedEntityAttribute tea = programAttribute(program, ValueType.FILE_RESOURCE);
 
     enroll(trackedEntity, program, orgUnit);
-
+    this.switchContextToUser(user);
     assertStartsWith(
-        "Attribute value for tracked entity attribute " + tea.getUid(),
+        "TrackedEntityAttribute with id " + tea.getUid(),
         GET(
                 "/tracker/trackedEntities/{trackedEntityUid}/attributes/{attributeUid}/file?program={programUid}",
                 trackedEntity.getUid(),
@@ -999,7 +1003,7 @@ void getAttributeValuesFileByAttributeAndProgramIfFileResourceIsInDbButNotInTheS
     manager.save(file, false);
     TrackedEntityAttribute tea =
         addProgramAttributeValue(trackedEntity, program, ValueType.FILE_RESOURCE, file.getUid());
-
+    this.switchContextToUser(user);
     GET(
             "/tracker/trackedEntities/{trackedEntityUid}/attributes/{attributeUid}/file?program={programUid}",
             trackedEntity.getUid(),
@@ -1014,18 +1018,14 @@ void getAttributeValuesFileByAttributeAndProgramIfFileIsNotFound() {
     enroll(trackedEntity, program, orgUnit);
 
     String fileUid = CodeGenerator.generateUid();
-    TrackedEntityAttribute tea =
-        addProgramAttributeValue(trackedEntity, program, ValueType.FILE_RESOURCE, fileUid);
 
-    assertStartsWith(
-        "FileResource with id " + fileUid,
-        GET(
-                "/tracker/trackedEntities/{trackedEntityUid}/attributes/{attributeUid}/file?program={programUid}",
-                trackedEntity.getUid(),
-                tea.getUid(),
-                program.getUid())
-            .error(HttpStatus.NOT_FOUND)
-            .getMessage());
+    IllegalQueryException exception =
+        assertThrows(
+            IllegalQueryException.class,
+            () ->
+                addProgramAttributeValue(trackedEntity, program, ValueType.FILE_RESOURCE, fileUid));
+
+    assertStartsWith("FileResource with id '" + fileUid, exception.getMessage());
   }
 
   @Test
@@ -1146,7 +1146,8 @@ private TrackedEntityType trackedEntityType(char uniqueChar) {
   }
 
   private TrackedEntity trackedEntity() {
-    TrackedEntity te = trackedEntity(orgUnit);
+    TrackedEntity te = trackedEntity(orgUnit, trackedEntityType);
+    te.setTrackedEntityType(trackedEntityType);
     manager.save(te, false);
     return te;
   }
@@ -1157,14 +1158,10 @@ private TrackedEntity trackedEntity(TrackedEntityType trackedEntityType) {
     return te;
   }
 
-  private TrackedEntity trackedEntity(OrganisationUnit orgUnit) {
-    return trackedEntity(orgUnit, trackedEntityType);
-  }
-
   private TrackedEntity trackedEntity(
       OrganisationUnit orgUnit, TrackedEntityType trackedEntityType) {
     TrackedEntity te = createTrackedEntity(orgUnit, trackedEntityType);
-    te.getSharing().setPublicAccess(AccessStringHelper.DEFAULT);
+    te.getSharing().setPublicAccess(AccessStringHelper.READ);
     te.getSharing().setOwner(owner);
     return te;
   }
@@ -1175,6 +1172,8 @@ private Enrollment enroll(
     manager.save(enrollment);
     trackedEntity.getEnrollments().add(enrollment);
     manager.update(trackedEntity);
+    trackedEntityProgramOwnerService.createTrackedEntityProgramOwner(
+        trackedEntity, program, orgUnit);
 
     return enrollment;
   }
@@ -1289,16 +1288,22 @@ private TrackedEntityAttribute addTrackedEntityTypeAttributeValue(
       TrackedEntity trackedEntity, ValueType type, String value) {
     TrackedEntityAttribute tea =
         trackedEntityTypeAttribute(trackedEntity.getTrackedEntityType(), type);
-    trackedEntity.addAttributeValue(attributeValue(tea, trackedEntity, value));
+    TrackedEntityAttributeValue attributeValue = attributeValue(tea, trackedEntity, value);
+    trackedEntity.addAttributeValue(attributeValue);
     manager.save(trackedEntity, false);
+    trackedEntityAttributeValueService.addTrackedEntityAttributeValue(attributeValue);
+    manager.flush();
+    manager.clear();
     return tea;
   }
 
   private TrackedEntityAttribute addProgramAttributeValue(
       TrackedEntity trackedEntity, Program program, ValueType type, String value) {
     TrackedEntityAttribute tea = programAttribute(program, type);
-    trackedEntity.addAttributeValue(attributeValue(tea, trackedEntity, value));
+    TrackedEntityAttributeValue attributeValue = attributeValue(tea, trackedEntity, value);
+    trackedEntity.addAttributeValue(attributeValue);
     manager.save(trackedEntity, false);
+    trackedEntityAttributeValueService.addTrackedEntityAttributeValue(attributeValue);
     return tea;
   }
 
diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/notification/ProgramNotificationInstanceController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/notification/ProgramNotificationInstanceController.java
index 6997d918122d..8b345f82d8ce 100644
--- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/notification/ProgramNotificationInstanceController.java
+++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/notification/ProgramNotificationInstanceController.java
@@ -33,7 +33,9 @@
 
 import jakarta.servlet.http.HttpServletRequest;
 import java.util.List;
+import lombok.RequiredArgsConstructor;
 import org.hisp.dhis.common.DhisApiVersion;
+import org.hisp.dhis.common.IdentifiableObjectManager;
 import org.hisp.dhis.common.OpenApi;
 import org.hisp.dhis.common.OpenApi.Response.Status;
 import org.hisp.dhis.feedback.BadRequestException;
@@ -67,6 +69,7 @@
 @Controller
 @RequestMapping("/api/programNotificationInstances")
 @ApiVersion(include = {DhisApiVersion.DEFAULT, DhisApiVersion.ALL})
+@RequiredArgsConstructor
 public class ProgramNotificationInstanceController {
   private final ProgramNotificationInstanceService programNotificationInstanceService;
 
@@ -74,14 +77,7 @@ public class ProgramNotificationInstanceController {
 
   private final EventService eventService;
 
-  public ProgramNotificationInstanceController(
-      ProgramNotificationInstanceService programNotificationInstanceService,
-      EnrollmentService enrollmentService,
-      EventService eventService) {
-    this.programNotificationInstanceService = programNotificationInstanceService;
-    this.enrollmentService = enrollmentService;
-    this.eventService = eventService;
-  }
+  private final IdentifiableObjectManager manager;
 
   @OpenApi.Response(status = Status.OK, value = Page.class)
   @RequiresAuthority(anyOf = ALL)
@@ -93,11 +89,15 @@ public ProgramNotificationInstanceController(
 
     Event storedEvent = null;
     if (requestParams.getEvent() != null) {
-      storedEvent = eventService.getEvent(requestParams.getEvent());
+      eventService.getEvent(requestParams.getEvent());
+      // TODO(tracker) jdbc-hibernate: check the impact on performance
+      storedEvent = manager.get(Event.class, requestParams.getEvent());
     }
     Enrollment storedEnrollment = null;
     if (requestParams.getEnrollment() != null) {
-      storedEnrollment = enrollmentService.getEnrollment(requestParams.getEnrollment());
+      enrollmentService.getEnrollment(requestParams.getEnrollment());
+      // TODO(tracker) jdbc-hibernate: check the impact on performance
+      storedEnrollment = manager.get(Enrollment.class, requestParams.getEnrollment());
     }
 
     if (requestParams.isPaging()) {
diff --git a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportController.java b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportController.java
index a10df33a8d6a..0e805afdd644 100644
--- a/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportController.java
+++ b/dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportController.java
@@ -291,7 +291,7 @@ ResponseEntity<ObjectNode> getTrackedEntityByUid(
       @OpenApi.Param(value = String[].class) @RequestParam(defaultValue = DEFAULT_FIELDS_PARAM)
           List<FieldPath> fields,
       TrackerIdSchemeParams idSchemeParams)
-      throws ForbiddenException, NotFoundException, BadRequestException, WebMessageException {
+      throws ForbiddenException, NotFoundException, WebMessageException {
     TrackedEntityParams trackedEntityParams = fieldsMapper.map(fields);
 
     MappingErrors errors = new MappingErrors(idSchemeParams);
@@ -299,7 +299,7 @@ ResponseEntity<ObjectNode> getTrackedEntityByUid(
         TRACKED_ENTITY_MAPPER.map(
             idSchemeParams,
             errors,
-            trackedEntityService.getTrackedEntity(uid, program, trackedEntityParams));
+            trackedEntityService.getNewTrackedEntity(uid, program, trackedEntityParams));
     ensureNoMappingErrors(errors);
 
     return ResponseEntity.ok(fieldFilterService.toObjectNode(trackedEntity, fields));
@@ -314,11 +314,7 @@ void getTrackedEntityByUidAsCsv(
       HttpServletResponse response,
       @RequestParam(required = false, defaultValue = "false") boolean skipHeader,
       @OpenApi.Param({UID.class, Program.class}) @RequestParam(required = false) UID program)
-      throws IOException,
-          ForbiddenException,
-          NotFoundException,
-          BadRequestException,
-          WebMessageException {
+      throws IOException, ForbiddenException, NotFoundException, WebMessageException {
     TrackedEntityParams trackedEntityParams = fieldsMapper.map(CSV_FIELDS);
 
     MappingErrors errors = new MappingErrors(idSchemeParams);
@@ -326,7 +322,7 @@ void getTrackedEntityByUidAsCsv(
         TRACKED_ENTITY_MAPPER.map(
             idSchemeParams,
             errors,
-            trackedEntityService.getTrackedEntity(uid, program, trackedEntityParams));
+            trackedEntityService.getNewTrackedEntity(uid, program, trackedEntityParams));
     ensureNoMappingErrors(errors);
 
     OutputStream outputStream = response.getOutputStream();
@@ -342,7 +338,7 @@ ResponseEntity<InputStreamResource> getAttributeValueFile(
       @OpenApi.Param({UID.class, Attribute.class}) @PathVariable UID attribute,
       @OpenApi.Param({UID.class, Program.class}) @RequestParam(required = false) UID program,
       HttpServletRequest request)
-      throws NotFoundException, ConflictException, BadRequestException {
+      throws NotFoundException, ConflictException, BadRequestException, ForbiddenException {
     validateUnsupportedParameter(
         request,
         "dimension",
@@ -361,7 +357,7 @@ ResponseEntity<InputStreamResource> getAttributeValueImage(
       @OpenApi.Param({UID.class, Program.class}) @RequestParam(required = false) UID program,
       @RequestParam(required = false) ImageFileDimension dimension,
       HttpServletRequest request)
-      throws NotFoundException, ConflictException, BadRequestException {
+      throws NotFoundException, ConflictException, BadRequestException, ForbiddenException {
     return requestHandler.serve(
         request,
         trackedEntityService.getFileResourceImage(trackedEntity, attribute, program, dimension));
diff --git a/dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventRequestParamsMapperTest.java b/dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventRequestParamsMapperTest.java
index 6384b922a865..9ef8763d3c2e 100644
--- a/dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventRequestParamsMapperTest.java
+++ b/dhis-2/dhis-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/event/EventRequestParamsMapperTest.java
@@ -133,7 +133,7 @@ class EventRequestParamsMapperTest {
   private TrackerIdSchemeParams idSchemeParams;
 
   @BeforeEach
-  void setUp() throws ForbiddenException, NotFoundException, BadRequestException {
+  void setUp() throws ForbiddenException, NotFoundException {
     User user = new User();
 
     when(userService.getUserByUsername(null)).thenReturn(user);
@@ -153,7 +153,7 @@ void setUp() throws ForbiddenException, NotFoundException, BadRequestException {
     when(organisationUnitService.isInUserHierarchy(user, orgUnit)).thenReturn(true);
 
     TrackedEntity trackedEntity = new TrackedEntity();
-    when(trackedEntityService.getTrackedEntity(UID.of("qnR1RK4cTIZ"), null, FALSE))
+    when(trackedEntityService.getNewTrackedEntity(UID.of("qnR1RK4cTIZ"), null, FALSE))
         .thenReturn(trackedEntity);
     TrackedEntityAttribute tea1 = new TrackedEntityAttribute();
     tea1.setUid(TEA_1_UID.getValue());


Reviews:
user: login: teleivo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T14:22:37Z
user: login: muilpp
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T15:13:34Z
user: login: teleivo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T15:42:27Z
user: login: enricocolasante
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:00:57Z
user: login: enricocolasante
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:03:07Z
user: login: enricocolasante
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T10:25:20Z
user: login: teleivo
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T12:19:54Z
user: login: enricocolasante
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T12:26:15Z

Review comments:
diff_hunk: @@ -116,18 +112,17 @@ public FileResourceStream getFileResourceImage(
       @Nonnull UID attribute,
       @CheckForNull UID program,
       ImageFileDimension dimension)
-      throws NotFoundException {
+      throws NotFoundException, ForbiddenException {
     FileResource fileResource = getFileResourceMetadata(trackedEntity, attribute, program);
     return FileResourceStream.ofImage(fileResourceService, fileResource, dimension);
   }
 
   private FileResource getFileResourceMetadata(
       UID trackedEntityUid, UID attributeUid, @CheckForNull UID programUid)
-      throws NotFoundException {
-    TrackedEntity trackedEntity = trackedEntityStore.getByUid(trackedEntityUid.getValue());
-    if (trackedEntity == null || trackedEntity.isDeleted()) {
-      throw new NotFoundException(TrackedEntity.class, trackedEntityUid.getValue());
-    }
+      throws NotFoundException, ForbiddenException {
+    TrackedEntity trackedEntity =
+        getNewTrackedEntity(
+            trackedEntityUid, programUid, TrackedEntityParams.FALSE.withIncludeAttributes(true));
 
     TrackedEntityAttribute attribute = getAttribute(attributeUid, trackedEntity, programUid);
path: dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/DefaultTrackedEntityService.java
user: login: teleivo
body: is the attribute logic regarding program vs tet attributes still needed in `getAttribute` now that we use `getNewTrackedEntity` passing in the program and retrieving the attributes?
created_at: 2025-03-03T14:18:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -513,8 +512,8 @@ void getTrackedEntityCsvById() {
     assertTrue(response.header("content-disposition").contains("filename=trackedEntity.csv"));
     assertStartsWith(
         """
-trackedEntity,trackedEntityType,createdAt,createdAtClient,updatedAt,updatedAtClient,orgUnit,inactive,deleted,potentialDuplicate,geometry,latitude,longitude,storedBy,createdBy,updatedBy,attrCreatedAt,attrUpdatedAt,attribute,displayName,value,valueType
-""",
+    trackedEntity,trackedEntityType,createdAt,createdAtClient,updatedAt,updatedAtClient,orgUnit,inactive,deleted,potentialDuplicate,geometry,latitude,longitude,storedBy,createdBy,updatedBy,attrCreatedAt,attrUpdatedAt,attribute,displayName,value,valueType
path: dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerTest.java
user: login: teleivo
body: is this added whitespace now in the csv 🤔 
created_at: 2025-03-03T14:20:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1014,18 +1014,14 @@ void getAttributeValuesFileByAttributeAndProgramIfFileIsNotFound() {
     enroll(trackedEntity, program, orgUnit);
 
     String fileUid = CodeGenerator.generateUid();
-    TrackedEntityAttribute tea =
-        addProgramAttributeValue(trackedEntity, program, ValueType.FILE_RESOURCE, fileUid);
 
-    assertStartsWith(
-        "FileResource with id " + fileUid,
-        GET(
-                "/tracker/trackedEntities/{trackedEntityUid}/attributes/{attributeUid}/file?program={programUid}",
-                trackedEntity.getUid(),
-                tea.getUid(),
-                program.getUid())
-            .error(HttpStatus.NOT_FOUND)
-            .getMessage());
+    IllegalQueryException illegalQueryException =
path: dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerTest.java
user: login: teleivo
body: ```suggestion
    IllegalQueryException exception =
```
created_at: 2025-03-03T14:20:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1014,18 +1014,14 @@ void getAttributeValuesFileByAttributeAndProgramIfFileIsNotFound() {
     enroll(trackedEntity, program, orgUnit);
 
     String fileUid = CodeGenerator.generateUid();
-    TrackedEntityAttribute tea =
-        addProgramAttributeValue(trackedEntity, program, ValueType.FILE_RESOURCE, fileUid);
 
-    assertStartsWith(
-        "FileResource with id " + fileUid,
-        GET(
-                "/tracker/trackedEntities/{trackedEntityUid}/attributes/{attributeUid}/file?program={programUid}",
-                trackedEntity.getUid(),
-                tea.getUid(),
-                program.getUid())
-            .error(HttpStatus.NOT_FOUND)
-            .getMessage());
+    IllegalQueryException illegalQueryException =
path: dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerTest.java
user: login: teleivo
body: Why do we now get a `IllegalQueryException` if the file is not found? I think this should be a 404 🤔 
created_at: 2025-03-03T14:21:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -834,7 +833,7 @@ void getAttributeValuesFileByAttributeAndProgramIfAttributeIsNotFound() throws C
     addProgramAttributeValue(trackedEntity, program, ValueType.FILE_RESOURCE, file.getUid());
 
     String attributeUid = CodeGenerator.generateUid();
-
+    this.switchContextToUser(user);
path: dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerTest.java
user: login: muilpp
body: just out of curiosity, why do we use `switchContextToUser` instead of `injectSecurityContextUser`?
created_at: 2025-03-03T15:11:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -834,7 +833,7 @@ void getAttributeValuesFileByAttributeAndProgramIfAttributeIsNotFound() throws C
     addProgramAttributeValue(trackedEntity, program, ValueType.FILE_RESOURCE, file.getUid());
 
     String attributeUid = CodeGenerator.generateUid();
-
+    this.switchContextToUser(user);
path: dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerTest.java
user: login: teleivo
body: so far no one knows. we asked in the backend meeting and there was no clear answer. we should figure out what works for us and stick to one of the many. as this question comes up again and again 😢 
created_at: 2025-03-03T15:42:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -513,8 +512,8 @@ void getTrackedEntityCsvById() {
     assertTrue(response.header("content-disposition").contains("filename=trackedEntity.csv"));
     assertStartsWith(
         """
-trackedEntity,trackedEntityType,createdAt,createdAtClient,updatedAt,updatedAtClient,orgUnit,inactive,deleted,potentialDuplicate,geometry,latitude,longitude,storedBy,createdBy,updatedBy,attrCreatedAt,attrUpdatedAt,attribute,displayName,value,valueType
-""",
+    trackedEntity,trackedEntityType,createdAt,createdAtClient,updatedAt,updatedAtClient,orgUnit,inactive,deleted,potentialDuplicate,geometry,latitude,longitude,storedBy,createdBy,updatedBy,attrCreatedAt,attrUpdatedAt,attribute,displayName,value,valueType
path: dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerTest.java
user: login: enricocolasante
body: <img width="1119" alt="Screenshot 2025-03-04 at 09 00 08" src="https://github.com/user-attachments/assets/952cdc85-c61a-4a09-8e40-c8d1e323f163" />

I didn't change anything here and git blame is weird 🤔 
created_at: 2025-03-04T08:00:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1014,18 +1014,14 @@ void getAttributeValuesFileByAttributeAndProgramIfFileIsNotFound() {
     enroll(trackedEntity, program, orgUnit);
 
     String fileUid = CodeGenerator.generateUid();
-    TrackedEntityAttribute tea =
-        addProgramAttributeValue(trackedEntity, program, ValueType.FILE_RESOURCE, fileUid);
 
-    assertStartsWith(
-        "FileResource with id " + fileUid,
-        GET(
-                "/tracker/trackedEntities/{trackedEntityUid}/attributes/{attributeUid}/file?program={programUid}",
-                trackedEntity.getUid(),
-                tea.getUid(),
-                program.getUid())
-            .error(HttpStatus.NOT_FOUND)
-            .getMessage());
+    IllegalQueryException illegalQueryException =
path: dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerTest.java
user: login: enricocolasante
body: Yes, it should.
But the whole `trackedEntityAttributeValueService` is using unchecked exceptions.
I can fix it in a next PR.
created_at: 2025-03-04T08:03:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -116,18 +112,17 @@ public FileResourceStream getFileResourceImage(
       @Nonnull UID attribute,
       @CheckForNull UID program,
       ImageFileDimension dimension)
-      throws NotFoundException {
+      throws NotFoundException, ForbiddenException {
     FileResource fileResource = getFileResourceMetadata(trackedEntity, attribute, program);
     return FileResourceStream.ofImage(fileResourceService, fileResource, dimension);
   }
 
   private FileResource getFileResourceMetadata(
       UID trackedEntityUid, UID attributeUid, @CheckForNull UID programUid)
-      throws NotFoundException {
-    TrackedEntity trackedEntity = trackedEntityStore.getByUid(trackedEntityUid.getValue());
-    if (trackedEntity == null || trackedEntity.isDeleted()) {
-      throw new NotFoundException(TrackedEntity.class, trackedEntityUid.getValue());
-    }
+      throws NotFoundException, ForbiddenException {
+    TrackedEntity trackedEntity =
+        getNewTrackedEntity(
+            trackedEntityUid, programUid, TrackedEntityParams.FALSE.withIncludeAttributes(true));
 
     TrackedEntityAttribute attribute = getAttribute(attributeUid, trackedEntity, programUid);
path: dhis-2/dhis-services/dhis-service-tracker/src/main/java/org/hisp/dhis/tracker/export/trackedentity/DefaultTrackedEntityService.java
user: login: enricocolasante
body: Removed all the logic about attributes from here and fix the code in `TrackedEntityAggregate`
created_at: 2025-03-04T10:25:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1014,18 +1018,14 @@ void getAttributeValuesFileByAttributeAndProgramIfFileIsNotFound() {
     enroll(trackedEntity, program, orgUnit);
 
     String fileUid = CodeGenerator.generateUid();
-    TrackedEntityAttribute tea =
-        addProgramAttributeValue(trackedEntity, program, ValueType.FILE_RESOURCE, fileUid);
 
-    assertStartsWith(
-        "FileResource with id " + fileUid,
-        GET(
-                "/tracker/trackedEntities/{trackedEntityUid}/attributes/{attributeUid}/file?program={programUid}",
-                trackedEntity.getUid(),
-                tea.getUid(),
-                program.getUid())
-            .error(HttpStatus.NOT_FOUND)
-            .getMessage());
+    IllegalQueryException exception =
+        assertThrows(
+            IllegalQueryException.class,
path: dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerTest.java
user: login: teleivo
body: this is the one you are going to change back to a 404 right?
created_at: 2025-03-04T12:19:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1014,18 +1018,14 @@ void getAttributeValuesFileByAttributeAndProgramIfFileIsNotFound() {
     enroll(trackedEntity, program, orgUnit);
 
     String fileUid = CodeGenerator.generateUid();
-    TrackedEntityAttribute tea =
-        addProgramAttributeValue(trackedEntity, program, ValueType.FILE_RESOURCE, fileUid);
 
-    assertStartsWith(
-        "FileResource with id " + fileUid,
-        GET(
-                "/tracker/trackedEntities/{trackedEntityUid}/attributes/{attributeUid}/file?program={programUid}",
-                trackedEntity.getUid(),
-                tea.getUid(),
-                program.getUid())
-            .error(HttpStatus.NOT_FOUND)
-            .getMessage());
+    IllegalQueryException exception =
+        assertThrows(
+            IllegalQueryException.class,
path: dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/tracker/export/trackedentity/TrackedEntitiesExportControllerTest.java
user: login: enricocolasante
body: Yes ;)
created_at: 2025-03-04T12:26:15Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-03-04T10:15:02Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=dhis2_dhis2-core&pullRequest=20126) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [3 New issues](https://sonarcloud.io/project/issues?id=dhis2_dhis2-core&pullRequest=20126&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [3 New Code Smells](https://sonarcloud.io/dashboard?id=dhis2_dhis2-core&pullRequest=20126) (required ≤ 0)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=dhis2_dhis2-core&pullRequest=20126)

##   
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png '') Catch issues before they fail your Quality Gate with our IDE extension ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png '') [SonarQube for IDE](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=pull-request)


author_association: NONE