From 08e46be74e731c2f5c3d045137833ea839089942 Mon Sep 17 00:00:00 2001
From: Pablo <pablo@dhis2.org>
Date: Fri, 9 Feb 2024 09:15:02 +0100
Subject: [PATCH] cache some complex operations

Signed-off-by: Pablo <pablo@dhis2.org>
---
 .../SearchRepositoryImpl.java                 | 170 +++++++++++-------
 .../dhis2/commons/data/SearchTeiModel.java    |   5 -
 2 files changed, 102 insertions(+), 73 deletions(-)

diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImpl.java b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImpl.java
index 2c89d5d569..2e548590ef 100644
--- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImpl.java
+++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImpl.java
@@ -4,15 +4,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.Transformations;
-import androidx.paging.DataSource;
-import androidx.paging.LivePagedListBuilder;
-import androidx.paging.PagedList;
-import androidx.paging.Pager;
-import androidx.paging.PagingData;
-import androidx.paging.PagingDataTransforms;
-import androidx.paging.PagingLiveData;
 
 import org.dhis2.R;
 import org.dhis2.bindings.ExtensionsKt;
@@ -31,6 +22,7 @@
 import org.dhis2.commons.filters.data.FilterPresenter;
 import org.dhis2.commons.filters.sorting.SortingItem;
 import org.dhis2.commons.network.NetworkUtils;
+import org.dhis2.commons.prefs.PreferenceProvider;
 import org.dhis2.commons.reporting.CrashReportController;
 import org.dhis2.commons.resources.ResourceManager;
 import org.dhis2.data.dhislogic.DhisEnrollmentUtils;
@@ -102,11 +94,6 @@
 import io.reactivex.Flowable;
 import io.reactivex.Observable;
 import io.reactivex.Single;
-import kotlin.Unit;
-import kotlin.coroutines.Continuation;
-import kotlinx.coroutines.ExecutorsKt;
-import kotlinx.coroutines.flow.Flow;
-import kotlinx.coroutines.flow.FlowCollector;
 
 public class SearchRepositoryImpl implements SearchRepository {
 
@@ -128,6 +115,14 @@ public class SearchRepositoryImpl implements SearchRepository {
     private ThemeManager themeManager;
     private HashSet<String> fetchedTeiUids = new HashSet<>();
     private TeiDownloader teiDownloader;
+    private HashMap<String, Program> programCache = new HashMap<>();
+    private  HashMap<String, String> orgUnitNameCache = new HashMap<>();
+
+    private HashMap<String, String> profilePictureCache = new HashMap<>();
+
+    private HashMap<String, List<String>> attributesUidsCache = new HashMap();
+
+    private HashMap<String, List<String>> trackedEntityTypeAttributesUidsCache = new HashMap();
 
     SearchRepositoryImpl(String teiType,
                          @Nullable String initialProgram,
@@ -162,6 +157,7 @@ public class SearchRepositoryImpl implements SearchRepository {
                 resources);
     }
 
+
     @Override
     public Observable<List<Program>> programsWithRegistration(String programTypeId) {
         return d2.organisationUnitModule().organisationUnits().byOrganisationUnitScope(OrganisationUnit.Scope.SCOPE_DATA_CAPTURE).get()
@@ -302,7 +298,7 @@ public Observable<Pair<String, String>> saveToEnroll(@NonNull String teiType,
                                         .organisationUnit(orgUnit)
                                         .build())
                         .map(enrollmentUid -> {
-                            boolean displayIncidentDate = d2.programModule().programs().uid(programUid).blockingGet().displayIncidentDate();
+                            boolean displayIncidentDate = getProgram(programUid).displayIncidentDate();
                             Date enrollmentDateNoTime = DateUtils.getInstance().getNextPeriod(PeriodType.Daily, enrollmentDate, 0);
                             d2.enrollmentModule().enrollments().uid(enrollmentUid).setEnrollmentDate(enrollmentDateNoTime);
                             if (displayIncidentDate) {
@@ -338,7 +334,7 @@ private void setEnrollmentInfo(SearchTeiModel searchTei) {
             if (enrollments.indexOf(enrollment) == 0)
                 searchTei.resetEnrollments();
             searchTei.addEnrollment(enrollment);
-            Program program = d2.programModule().programs().byUid().eq(enrollment.program()).one().blockingGet();
+            Program program = getProgram(enrollment.program());
             if (program.displayFrontPageList()) {
                 searchTei.addProgramInfo(program);
             }
@@ -480,8 +476,8 @@ private void setRelationshipsInfo(@NonNull SearchTeiModel searchTeiModel, Progra
                         RelationshipOwnerType.TEI,
                         fromValues,
                         toValues,
-                        ExtensionsKt.profilePicturePath(fromTei, d2, selectedProgram.uid()),
-                        ExtensionsKt.profilePicturePath(toTei, d2, selectedProgram.uid()),
+                        profilePicturePath(fromTei, selectedProgram.uid()),
+                        profilePicturePath(toTei, selectedProgram.uid()),
                         getTeiDefaultRes(fromTei),
                         getTeiDefaultRes(toTei),
                         -1,
@@ -493,6 +489,45 @@ private void setRelationshipsInfo(@NonNull SearchTeiModel searchTeiModel, Progra
         searchTeiModel.setRelationships(relationshipViewModels);
     }
 
+    private String profilePicturePath(TrackedEntityInstance tei, String programUid){
+        if(!profilePictureCache.containsKey(tei.uid())){
+            profilePictureCache.put(tei.uid(),ExtensionsKt.profilePicturePath(tei, d2, programUid));
+        }
+        return profilePictureCache.get(tei.uid());
+    }
+
+    private List<String> getProgramAttributeUids(String programUid) {
+        if(!attributesUidsCache.containsKey(programUid)){
+            List<String> attributeUids = new ArrayList<>();
+            List<ProgramTrackedEntityAttribute> programTrackedEntityAttributes = d2.programModule().programTrackedEntityAttributes()
+                    .byProgram().eq(programUid)
+                    .byDisplayInList().isTrue()
+                    .orderBySortOrder(RepositoryScope.OrderByDirection.ASC)
+                    .blockingGet();
+            for (ProgramTrackedEntityAttribute programAttribute : programTrackedEntityAttributes) {
+                attributeUids.add(programAttribute.trackedEntityAttribute().uid());
+            }
+            attributesUidsCache.put(programUid, attributeUids);
+        }
+
+        return attributesUidsCache.get(programUid);
+    }
+
+    private List<String> getTETypeAttributeUids(String teTypeUid){
+        if(!trackedEntityTypeAttributesUidsCache.containsKey(teTypeUid)){
+            List<String> attributeUids = new ArrayList<>();
+            List<TrackedEntityTypeAttribute> typeAttributes = d2.trackedEntityModule().trackedEntityTypeAttributes()
+                    .byTrackedEntityTypeUid().eq(teTypeUid)
+                    .byDisplayInList().isTrue()
+                    .blockingGet();
+
+            for (TrackedEntityTypeAttribute typeAttribute : typeAttributes) {
+                attributeUids.add(typeAttribute.trackedEntityAttribute().uid());
+            }
+        }
+        return trackedEntityTypeAttributesUidsCache.get(teTypeUid);
+    }
+
     private int getTeiDefaultRes(TrackedEntityInstance tei) {
         TrackedEntityType teiType = d2.trackedEntityModule().trackedEntityTypes().uid(tei.trackedEntityType()).blockingGet();
         return resources.getObjectStyleDrawableResource(teiType.style().icon(), R.drawable.photo_temp_gray);
@@ -501,32 +536,14 @@ private int getTeiDefaultRes(TrackedEntityInstance tei) {
     private List<TrackedEntityAttributeValue> getTrackedEntityAttributesForRelationship(TrackedEntityInstance tei, Program selectedProgram) {
 
         List<TrackedEntityAttributeValue> values;
-        List<String> attributeUids = new ArrayList<>();
-        List<ProgramTrackedEntityAttribute> programTrackedEntityAttributes = d2.programModule().programTrackedEntityAttributes()
-                .byProgram().eq(selectedProgram.uid())
-                .byDisplayInList().isTrue()
-                .orderBySortOrder(RepositoryScope.OrderByDirection.ASC)
-                .blockingGet();
-        for (ProgramTrackedEntityAttribute programAttribute : programTrackedEntityAttributes) {
-            attributeUids.add(programAttribute.trackedEntityAttribute().uid());
-        }
         values = d2.trackedEntityModule().trackedEntityAttributeValues()
                 .byTrackedEntityInstance().eq(tei.uid())
-                .byTrackedEntityAttribute().in(attributeUids).blockingGet();
+                .byTrackedEntityAttribute().in(getProgramAttributeUids(selectedProgram.uid())).blockingGet();
 
         if (values.isEmpty()) {
-            attributeUids.clear();
-            List<TrackedEntityTypeAttribute> typeAttributes = d2.trackedEntityModule().trackedEntityTypeAttributes()
-                    .byTrackedEntityTypeUid().eq(tei.trackedEntityType())
-                    .byDisplayInList().isTrue()
-                    .blockingGet();
-
-            for (TrackedEntityTypeAttribute typeAttribute : typeAttributes) {
-                attributeUids.add(typeAttribute.trackedEntityAttribute().uid());
-            }
             values = d2.trackedEntityModule().trackedEntityAttributeValues()
                     .byTrackedEntityInstance().eq(tei.uid())
-                    .byTrackedEntityAttribute().in(attributeUids).blockingGet();
+                    .byTrackedEntityAttribute().in(getTETypeAttributeUids(tei.trackedEntityType())).blockingGet();
         }
 
         return values;
@@ -534,7 +551,8 @@ private List<TrackedEntityAttributeValue> getTrackedEntityAttributesForRelations
 
     @Override
     public String getProgramColor(@NonNull String programUid) {
-        Program program = d2.programModule().programs().byUid().eq(programUid).one().blockingGet();
+        Program program = getProgram(programUid);
+        if(program == null) return "";
         return program.style() != null ?
                 program.style().color() != null ?
                         program.style().color() :
@@ -607,33 +625,33 @@ public List<EventViewModel> getEventsForMap(List<SearchTeiModel> teis) {
                 .byDeleted().isFalse()
                 .blockingGet();
 
-        for (Event event : events) {
-            ProgramStage stage = d2.programModule().programStages()
-                    .uid(event.programStage())
-                    .blockingGet();
+        HashMap<String,ProgramStage> cacheStages = new HashMap<>();
 
-            OrganisationUnit organisationUnit = d2.organisationUnitModule()
-                    .organisationUnits()
-                    .uid(event.organisationUnit())
-                    .blockingGet();
+        for (Event event : events) {
+            if(!cacheStages.containsKey(event.programStage())){
+                ProgramStage stage = d2.programModule().programStages()
+                        .uid(event.programStage())
+                        .blockingGet();
+                cacheStages.put(event.programStage(), stage);
+            }
 
             eventViewModels.add(
                     new EventViewModel(
                             EventViewModelType.EVENT,
-                            stage,
+                            cacheStages.get(event.programStage()),
                             event,
                             0,
                             null,
                             true,
                             true,
-                            organisationUnit.displayName(),
+                            orgUnitName(event.organisationUnit()),
                             null,
                             null,
                             false,
                             false,
                             false,
                             false,
-                            periodUtils.getPeriodUIString(stage.periodType(), event.eventDate() != null ? event.eventDate() : event.dueDate(), Locale.getDefault()),
+                            periodUtils.getPeriodUIString(cacheStages.get(event.programStage()).periodType(), event.eventDate() != null ? event.eventDate() : event.dueDate(), Locale.getDefault()),
                             null
                     ));
         }
@@ -641,6 +659,17 @@ public List<EventViewModel> getEventsForMap(List<SearchTeiModel> teis) {
         return eventViewModels;
     }
 
+    private String orgUnitName(String orgUnitUid){
+        if(!orgUnitNameCache.containsKey(orgUnitUid)){
+            OrganisationUnit organisationUnit = d2.organisationUnitModule()
+                    .organisationUnits()
+                    .uid(orgUnitUid)
+                    .blockingGet();
+            orgUnitNameCache.put(orgUnitUid, organisationUnit.displayName());
+        }
+        return orgUnitNameCache.get(orgUnitUid);
+    }
+
     @Override
     public SearchTeiModel getTrackedEntityInfo(String teiUid, Program selectedProgram, SortingItem sortingItem) {
         return transform(
@@ -727,20 +756,21 @@ public SearchTeiModel transform(TrackedEntitySearchItem searchItem, @Nullable Pr
         SearchTeiModel searchTei = new SearchTeiModel();
         if (dbTei != null && dbTei.aggregatedSyncState() != State.RELATIONSHIP) {
             searchTei.setTei(dbTei);
-            if (selectedProgram != null && d2.enrollmentModule().enrollments().byTrackedEntityInstance().eq(dbTei.uid()).byProgram().eq(selectedProgram.uid()).one().blockingExists()) {
-                List<Enrollment> possibleEnrollments = d2.enrollmentModule().enrollments()
-                        .byTrackedEntityInstance().eq(dbTei.uid())
-                        .byProgram().eq(selectedProgram.uid())
-                        .orderByEnrollmentDate(RepositoryScope.OrderByDirection.DESC)
-                        .blockingGet();
-                for (Enrollment enrollment : possibleEnrollments) {
+            List<Enrollment> enrollmentsInProgram = d2.enrollmentModule().enrollments()
+                    .byTrackedEntityInstance().eq(dbTei.uid())
+                    .byProgram().eq(selectedProgram.uid())
+                    .orderByEnrollmentDate(RepositoryScope.OrderByDirection.DESC)
+                    .blockingGet();
+
+            if (!enrollmentsInProgram.isEmpty()) {
+                for (Enrollment enrollment : enrollmentsInProgram) {
                     if (enrollment.status() == EnrollmentStatus.ACTIVE) {
                         searchTei.setCurrentEnrollment(enrollment);
                         break;
                     }
                 }
                 if (searchTei.getSelectedEnrollment() == null) {
-                    searchTei.setCurrentEnrollment(possibleEnrollments.get(0));
+                    searchTei.setCurrentEnrollment(enrollmentsInProgram.get(0));
                 }
             }
 
@@ -749,7 +779,7 @@ public SearchTeiModel transform(TrackedEntitySearchItem searchItem, @Nullable Pr
             if (offlineOnly)
                 searchTei.setOnline(!offlineOnly);
 
-            if (dbTei.deleted() != null && dbTei.deleted()) {
+            if (Boolean.TRUE.equals(dbTei.deleted())) {
                 searchTei.setOnline(true);
             }
 
@@ -760,14 +790,14 @@ public SearchTeiModel transform(TrackedEntitySearchItem searchItem, @Nullable Pr
                 setRelationshipsInfo(searchTei, selectedProgram);
             }
             if (searchTei.getSelectedEnrollment() != null) {
-                searchTei.setEnrolledOrgUnit(d2.organisationUnitModule().organisationUnits().uid(searchTei.getSelectedEnrollment().organisationUnit()).blockingGet().name());
+                searchTei.setEnrolledOrgUnit(orgUnitName(searchTei.getSelectedEnrollment().organisationUnit()));
             } else {
-                searchTei.setEnrolledOrgUnit(d2.organisationUnitModule().organisationUnits().uid(searchTei.getTei().organisationUnit()).blockingGet().name());
+                searchTei.setEnrolledOrgUnit(orgUnitName(searchTei.getTei().organisationUnit()));
             }
             searchTei.setProfilePicture(profilePicturePath(dbTei, selectedProgram));
         } else {
             searchTei.setTei(teiFromItem);
-            searchTei.setEnrolledOrgUnit(d2.organisationUnitModule().organisationUnits().uid(searchTei.getTei().organisationUnit()).blockingGet().name());
+            searchTei.setEnrolledOrgUnit(orgUnitName(searchTei.getTei().organisationUnit()));
 
             for (TrackedEntitySearchItemAttribute attribute : searchItem.getAttributeValues()) {
                 if (attribute.getDisplayInList()) {
@@ -882,9 +912,13 @@ public boolean eventsHaveCoordinates(String programUid) {
     @Nullable
     @Override
     public Program getProgram(@Nullable String programUid) {
-        if (programUid == null)
-            return null;
-        return d2.programModule().programs().uid(programUid).blockingGet();
+        if (programUid == null) return null;
+
+        if (!programCache.containsKey(programUid)) {
+            Program program = d2.programModule().programs().uid(programUid).blockingGet();
+            programCache.put(program.uid(), program);
+        }
+        return programCache.get(programUid);
     }
 
     @Override
@@ -929,8 +963,8 @@ public boolean canCreateInProgramWithoutSearch() {
     }
 
     private boolean displayOrgUnit() {
-           return d2.organisationUnitModule().organisationUnits()
-               .byProgramUids(Collections.singletonList(currentProgram))
-               .blockingGet().size() > 1;
+        return d2.organisationUnitModule().organisationUnits()
+                .byProgramUids(Collections.singletonList(currentProgram))
+                .blockingGet().size() > 1;
     }
 }
diff --git a/commons/src/main/java/org/dhis2/commons/data/SearchTeiModel.java b/commons/src/main/java/org/dhis2/commons/data/SearchTeiModel.java
index 96c4990542..7c96df5d79 100644
--- a/commons/src/main/java/org/dhis2/commons/data/SearchTeiModel.java
+++ b/commons/src/main/java/org/dhis2/commons/data/SearchTeiModel.java
@@ -111,11 +111,6 @@ public void resetEnrollments() {
         this.enrollmentsInfo.clear();
     }
 
-    public List<Trio<String, String, String>> getEnrollmentInfo() {
-        Collections.sort(enrollmentsInfo, (enrollment1, enrollment2) -> enrollment1.val0().compareToIgnoreCase(enrollment2.val0()));
-        return enrollmentsInfo;
-    }
-
     public void setAttributeValues(LinkedHashMap<String, TrackedEntityAttributeValue> attributeValues) {
         this.attributeValues = attributeValues;
     }