diff --git a/avni-server-api/src/main/java/org/avni/server/dao/EncounterSearchRepository.java b/avni-server-api/src/main/java/org/avni/server/dao/EncounterSearchRepository.java index 461aa93db..a4d4b4482 100644 --- a/avni-server-api/src/main/java/org/avni/server/dao/EncounterSearchRepository.java +++ b/avni-server-api/src/main/java/org/avni/server/dao/EncounterSearchRepository.java @@ -3,12 +3,10 @@ import org.avni.server.dao.search.EncounterSearchQueryBuilder; import org.avni.server.dao.search.SqlQuery; import org.avni.server.domain.Encounter; -import org.avni.server.framework.security.UserContextHolder; import org.avni.server.web.api.EncounterSearchRequest; import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; import javax.persistence.Query; import javax.transaction.Transactional; import java.math.BigInteger; @@ -16,10 +14,11 @@ @Repository @Transactional -public class EncounterSearchRepository { +public class EncounterSearchRepository extends RoleSwitchableRepository{ - @PersistenceContext - private EntityManager entityManager; + public EncounterSearchRepository(EntityManager entityManager) { + super(entityManager); + } @Transactional public List search(EncounterSearchRequest searchRequest) { @@ -38,16 +37,6 @@ public List search(EncounterSearchRequest searchRequest) { return resultList; } - private void setRoleBackToUser() { - Query setRoleBackToWhatever = entityManager.createNativeQuery("set role " + UserContextHolder.getOrganisation().getDbUser()); - setRoleBackToWhatever.executeUpdate(); - } - - private void setRoleToNone() { - Query resetQuery = entityManager.createNativeQuery("reset role;"); - resetQuery.executeUpdate(); - } - public long getCount(EncounterSearchRequest searchRequest) { setRoleToNone(); diff --git a/avni-server-api/src/main/java/org/avni/server/dao/LocationMappingRepository.java b/avni-server-api/src/main/java/org/avni/server/dao/LocationMappingRepository.java index 57bfc2032..fdd90635d 100644 --- a/avni-server-api/src/main/java/org/avni/server/dao/LocationMappingRepository.java +++ b/avni-server-api/src/main/java/org/avni/server/dao/LocationMappingRepository.java @@ -2,6 +2,7 @@ import org.avni.server.domain.AddressLevel; import org.avni.server.domain.ParentLocationMapping; +import org.avni.server.framework.security.UserContextHolder; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Page; import org.springframework.data.jpa.repository.Query; @@ -22,12 +23,14 @@ public interface LocationMappingRepository extends ReferenceDataRepository al1.lineage and al.id <> al1.id\n" + " inner join location_location_mapping llm on al1.id = llm.location_id\n" + "where c.id = :catchmentId\n" + + " and c.organisation_id = :organisationId\n" + " and llm.last_modified_date_time between :lastModifiedDateTime and :now\n" + "order by llm.last_modified_date_time asc, llm.id asc", nativeQuery = true) Page getSyncResults( long catchmentId, Date lastModifiedDateTime, Date now, + long organisationId, Pageable pageable ); @@ -43,7 +46,8 @@ Page getSyncResults( @Override default Page getSyncResults(SyncParameters syncParameters) { - return getSyncResults(syncParameters.getCatchment().getId(), syncParameters.getLastModifiedDateTime().toDate(), syncParameters.getNow().toDate(), syncParameters.getPageable()); + Long organisationId = UserContextHolder.getOrganisation().getId(); + return getSyncResults(syncParameters.getCatchment().getId(), syncParameters.getLastModifiedDateTime().toDate(), syncParameters.getNow().toDate(), organisationId, syncParameters.getPageable()); } @Override diff --git a/avni-server-api/src/main/java/org/avni/server/dao/LocationMappingSyncRepository.java b/avni-server-api/src/main/java/org/avni/server/dao/LocationMappingSyncRepository.java new file mode 100644 index 000000000..e9ca7042a --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/dao/LocationMappingSyncRepository.java @@ -0,0 +1,48 @@ +package org.avni.server.dao; + +import org.avni.server.domain.AddressLevel; +import org.avni.server.domain.ParentLocationMapping; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Repository; + +import javax.persistence.EntityManager; +import javax.transaction.Transactional; + +/** + * LocationSyncRepository uses the postgres @> keyword for sync, which does not index well and creates poor plans. + * This is a performance enhancement that temporarily switches out the role of the user before running this query. + * + */ +@Repository +public class LocationMappingSyncRepository extends RoleSwitchableRepository implements SyncableRepository { + private LocationMappingRepository locationMappingRepository; + + public LocationMappingSyncRepository(EntityManager entityManager, LocationMappingRepository locationMappingRepository) { + super(entityManager); + this.locationMappingRepository = locationMappingRepository; + } + + @Override + public boolean isEntityChanged(SyncParameters syncParameters) { + return locationMappingRepository.isEntityChanged(syncParameters); + } + + @Override + public Slice getSyncResultsAsSlice(SyncParameters syncParameters) { + return locationMappingRepository.getSyncResultsAsSlice(syncParameters); + } + + + @Override + @Transactional + public Page getSyncResults(SyncParameters syncParameters) { + try { + setRoleToNone(); + Page syncResults = locationMappingRepository.getSyncResults(syncParameters); + return syncResults; + } finally { + setRoleBackToUser(); + } + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/dao/LocationRepository.java b/avni-server-api/src/main/java/org/avni/server/dao/LocationRepository.java index 1d39883a6..fe6ab98e6 100644 --- a/avni-server-api/src/main/java/org/avni/server/dao/LocationRepository.java +++ b/avni-server-api/src/main/java/org/avni/server/dao/LocationRepository.java @@ -42,9 +42,10 @@ public interface LocationRepository extends ReferenceDataRepository al1.lineage \n" + "where c.id = :catchmentId\n" + + " and c.organisation_id = :organisationId\n" + " and al1.last_modified_date_time between :lastModifiedDateTime and :now\n" + "order by al1.last_modified_date_time asc, al1.id asc", nativeQuery = true) - Page getSyncResults(long catchmentId, Date lastModifiedDateTime, Date now, Pageable pageable); + Page getSyncResults(long catchmentId, Date lastModifiedDateTime, Date now, long organisationId, Pageable pageable); @Query(value = "select count(*)\n" + "from catchment c\n" + @@ -105,7 +106,8 @@ public interface LocationRepository extends ReferenceDataRepository getSyncResults(SyncParameters syncParameters) { - return getSyncResults(syncParameters.getCatchment().getId(), syncParameters.getLastModifiedDateTime().toDate(), syncParameters.getNow().toDate(), syncParameters.getPageable()); + Long organisationId = UserContextHolder.getOrganisation().getId(); + return getSyncResults(syncParameters.getCatchment().getId(), syncParameters.getLastModifiedDateTime().toDate(), syncParameters.getNow().toDate(), organisationId, syncParameters.getPageable()); } @Override diff --git a/avni-server-api/src/main/java/org/avni/server/dao/LocationSyncRepository.java b/avni-server-api/src/main/java/org/avni/server/dao/LocationSyncRepository.java new file mode 100644 index 000000000..82c670326 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/dao/LocationSyncRepository.java @@ -0,0 +1,41 @@ +package org.avni.server.dao; + +import org.avni.server.domain.AddressLevel; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Repository; + +import javax.persistence.EntityManager; +import javax.transaction.Transactional; + +@Repository +public class LocationSyncRepository extends RoleSwitchableRepository implements SyncableRepository { + private LocationRepository locationRepository; + + public LocationSyncRepository(EntityManager entityManager, LocationRepository locationRepository) { + super(entityManager); + this.locationRepository = locationRepository; + } + + @Override + public boolean isEntityChanged(SyncParameters syncParameters) { + return locationRepository.isEntityChanged(syncParameters); + } + + @Override + public Slice getSyncResultsAsSlice(SyncParameters syncParameters) { + return locationRepository.getSyncResultsAsSlice(syncParameters); + } + + @Override + @Transactional + public Page getSyncResults(SyncParameters syncParameters) { + try { + setRoleToNone(); + Page syncResults = locationRepository.getSyncResults(syncParameters); + return syncResults; + } finally { + setRoleBackToUser(); + } + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/dao/OperatingIndividualScopeAwareRepository.java b/avni-server-api/src/main/java/org/avni/server/dao/OperatingIndividualScopeAwareRepository.java index 8e5733d1c..c450fc46e 100644 --- a/avni-server-api/src/main/java/org/avni/server/dao/OperatingIndividualScopeAwareRepository.java +++ b/avni-server-api/src/main/java/org/avni/server/dao/OperatingIndividualScopeAwareRepository.java @@ -18,7 +18,7 @@ @SuppressWarnings("rawtypes") @NoRepositoryBean -public interface OperatingIndividualScopeAwareRepository extends JpaSpecificationExecutor, CustomCHSJpaRepository { +public interface OperatingIndividualScopeAwareRepository extends JpaSpecificationExecutor, CustomCHSJpaRepository, SyncableRepository { default Specification getSpecification(SyncParameters syncParameters) { Specification specification; if (syncParameters.isModificationCheckOnEntity()) { @@ -35,16 +35,19 @@ default Specification getSpecification(SyncParameters syncParameters) { return specification; } + @Override default Slice getSyncResultsAsSlice(SyncParameters syncParameters) { Specification specification = getSpecification(syncParameters); return findAllAsSlice(specification, syncParameters.getPageable()); } + @Override default Page getSyncResults(SyncParameters syncParameters) { Specification specification = getSpecification(syncParameters); return findAll(specification, syncParameters.getPageable()); } + @Override boolean isEntityChanged(SyncParameters syncParameters); default Specification getAuditSpecification(SyncParameters syncParameters) { diff --git a/avni-server-api/src/main/java/org/avni/server/dao/RoleSwitchableRepository.java b/avni-server-api/src/main/java/org/avni/server/dao/RoleSwitchableRepository.java new file mode 100644 index 000000000..419847427 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/dao/RoleSwitchableRepository.java @@ -0,0 +1,26 @@ +package org.avni.server.dao; + +import org.avni.server.framework.security.UserContextHolder; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; + +public class RoleSwitchableRepository { + @PersistenceContext + protected EntityManager entityManager; + + public RoleSwitchableRepository(EntityManager entityManager) { + this.entityManager = entityManager; + } + + protected void setRoleBackToUser() { + Query setRoleBackToWhatever = entityManager.createNativeQuery("set role \"" + UserContextHolder.getOrganisation().getDbUser() + "\""); + setRoleBackToWhatever.executeUpdate(); + } + + protected void setRoleToNone() { + Query resetQuery = entityManager.createNativeQuery("reset role;"); + resetQuery.executeUpdate(); + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/dao/SyncableRepository.java b/avni-server-api/src/main/java/org/avni/server/dao/SyncableRepository.java new file mode 100644 index 000000000..d4ebdbf4d --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/dao/SyncableRepository.java @@ -0,0 +1,10 @@ +package org.avni.server.dao; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Slice; + +public interface SyncableRepository { + Page getSyncResults(SyncParameters syncParameters); + boolean isEntityChanged(SyncParameters syncParameters); + Slice getSyncResultsAsSlice(SyncParameters syncParameters); +} diff --git a/avni-server-api/src/main/java/org/avni/server/service/ScopeAwareService.java b/avni-server-api/src/main/java/org/avni/server/service/ScopeAwareService.java index 52bfd53d0..99a3201ad 100644 --- a/avni-server-api/src/main/java/org/avni/server/service/ScopeAwareService.java +++ b/avni-server-api/src/main/java/org/avni/server/service/ScopeAwareService.java @@ -1,7 +1,7 @@ package org.avni.server.service; -import org.avni.server.dao.OperatingIndividualScopeAwareRepository; import org.avni.server.dao.SyncParameters; +import org.avni.server.dao.SyncableRepository; import org.avni.server.dao.sync.SyncEntityName; import org.avni.server.domain.CHSEntity; import org.avni.server.domain.SubjectType; @@ -23,7 +23,7 @@ default boolean isChangedByCatchment(User user, DateTime lastModifiedDateTime, S return repository().isEntityChanged(new SyncParameters(lastModifiedDateTime, DateTime.now(), null, null, null, null, null, user.getSyncSettings(), syncEntityName, user.getCatchment())); } - OperatingIndividualScopeAwareRepository repository(); + SyncableRepository repository(); boolean isScopeEntityChanged(DateTime lastModifiedDateTime, String typeUUID); } diff --git a/avni-server-api/src/main/java/org/avni/server/service/ScopeBasedSyncService.java b/avni-server-api/src/main/java/org/avni/server/service/ScopeBasedSyncService.java index 66a1f2a41..70f50c8d2 100644 --- a/avni-server-api/src/main/java/org/avni/server/service/ScopeBasedSyncService.java +++ b/avni-server-api/src/main/java/org/avni/server/service/ScopeBasedSyncService.java @@ -1,12 +1,12 @@ package org.avni.server.service; import org.avni.server.dao.SyncParameters; +import org.avni.server.dao.SyncableRepository; import org.avni.server.dao.sync.SyncEntityName; import org.avni.server.domain.CHSEntity; import org.avni.server.domain.SubjectType; import org.avni.server.domain.User; import org.joda.time.DateTime; -import org.avni.server.dao.OperatingIndividualScopeAwareRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -22,31 +22,31 @@ public ScopeBasedSyncService(AddressLevelService addressLevelService) { this.addressLevelService = addressLevelService; } - public Page getSyncResultsBySubjectTypeRegistrationLocation(OperatingIndividualScopeAwareRepository repository, User user, DateTime lastModifiedDateTime, DateTime now, Long typeId, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) { + public Page getSyncResultsBySubjectTypeRegistrationLocation(SyncableRepository repository, User user, DateTime lastModifiedDateTime, DateTime now, Long typeId, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) { List addressLevels = addressLevelService.getAllRegistrationAddressIdsBySubjectType(user.getCatchment(), subjectType); return repository.getSyncResults(new SyncParameters(lastModifiedDateTime, now, typeId, null, pageable, addressLevels, subjectType, user.getSyncSettings(), syncEntityName, user.getCatchment())); } - public Page getSyncResultsBySubjectTypeRegistrationLocation(OperatingIndividualScopeAwareRepository repository, User user, DateTime lastModifiedDateTime, DateTime now, String entityTypeUuid, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) { + public Page getSyncResultsBySubjectTypeRegistrationLocation(SyncableRepository repository, User user, DateTime lastModifiedDateTime, DateTime now, String entityTypeUuid, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) { List addressLevels = addressLevelService.getAllRegistrationAddressIdsBySubjectType(user.getCatchment(), subjectType); return repository.getSyncResults(new SyncParameters(lastModifiedDateTime, now, null, entityTypeUuid, pageable, addressLevels, subjectType, user.getSyncSettings(), syncEntityName, user.getCatchment())); } - public Page getSyncResultsByCatchment(OperatingIndividualScopeAwareRepository repository, User user, DateTime lastModifiedDateTime, DateTime now, Pageable pageable, SyncEntityName syncEntityName) { + public Page getSyncResultsByCatchment(SyncableRepository repository, User user, DateTime lastModifiedDateTime, DateTime now, Pageable pageable, SyncEntityName syncEntityName) { return repository.getSyncResults(new SyncParameters(lastModifiedDateTime, now, null, null, pageable, null, null, user.getSyncSettings(), syncEntityName, user.getCatchment())); } - public Slice getSyncResultsBySubjectTypeRegistrationLocationAsSlice(OperatingIndividualScopeAwareRepository repository, User user, DateTime lastModifiedDateTime, DateTime now, Long typeId, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) { + public Slice getSyncResultsBySubjectTypeRegistrationLocationAsSlice(SyncableRepository repository, User user, DateTime lastModifiedDateTime, DateTime now, Long typeId, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) { List addressLevels = addressLevelService.getAllRegistrationAddressIdsBySubjectType(user.getCatchment(), subjectType); return repository.getSyncResultsAsSlice(new SyncParameters(lastModifiedDateTime, now, typeId, null, pageable, addressLevels, subjectType, user.getSyncSettings(), syncEntityName, user.getCatchment())); } - public Slice getSyncResultsBySubjectTypeRegistrationLocationAsSlice(OperatingIndividualScopeAwareRepository repository, User user, DateTime lastModifiedDateTime, DateTime now, String entityTypeUuid, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) { + public Slice getSyncResultsBySubjectTypeRegistrationLocationAsSlice(SyncableRepository repository, User user, DateTime lastModifiedDateTime, DateTime now, String entityTypeUuid, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) { List addressLevels = addressLevelService.getAllRegistrationAddressIdsBySubjectType(user.getCatchment(), subjectType); return repository.getSyncResultsAsSlice(new SyncParameters(lastModifiedDateTime, now, null, entityTypeUuid, pageable, addressLevels, subjectType, user.getSyncSettings(), syncEntityName, user.getCatchment())); } - public Slice getSyncResultsByCatchmentAsSlice(OperatingIndividualScopeAwareRepository repository, User user, DateTime lastModifiedDateTime, DateTime now, Pageable pageable, SyncEntityName syncEntityName) { + public Slice getSyncResultsByCatchmentAsSlice(SyncableRepository repository, User user, DateTime lastModifiedDateTime, DateTime now, Pageable pageable, SyncEntityName syncEntityName) { return repository.getSyncResultsAsSlice(new SyncParameters(lastModifiedDateTime, now, null, null, pageable, null, null, user.getSyncSettings(), syncEntityName, user.getCatchment())); } } diff --git a/avni-server-api/src/main/java/org/avni/server/web/LocationController.java b/avni-server-api/src/main/java/org/avni/server/web/LocationController.java index 5a9e60143..6284804aa 100644 --- a/avni-server-api/src/main/java/org/avni/server/web/LocationController.java +++ b/avni-server-api/src/main/java/org/avni/server/web/LocationController.java @@ -4,6 +4,7 @@ import org.avni.server.application.projections.LocationProjection; import org.avni.server.builder.BuilderException; import org.avni.server.dao.LocationRepository; +import org.avni.server.dao.LocationSyncRepository; import org.avni.server.dao.sync.SyncEntityName; import org.avni.server.domain.AddressLevel; import org.avni.server.domain.accessControl.PrivilegeType; @@ -45,14 +46,16 @@ public class LocationController implements RestControllerResourceProcessor scopeBasedSyncService; private final AccessControlService accessControlService; + private LocationSyncRepository locationSyncRepository; @Autowired - public LocationController(LocationRepository locationRepository, UserService userService, LocationService locationService, ScopeBasedSyncService scopeBasedSyncService, AccessControlService accessControlService) { + public LocationController(LocationRepository locationRepository, UserService userService, LocationService locationService, ScopeBasedSyncService scopeBasedSyncService, AccessControlService accessControlService, LocationSyncRepository locationSyncRepository) { this.locationRepository = locationRepository; this.userService = userService; this.locationService = locationService; this.scopeBasedSyncService = scopeBasedSyncService; this.accessControlService = accessControlService; + this.locationSyncRepository = locationSyncRepository; this.logger = LoggerFactory.getLogger(this.getClass()); } @@ -111,7 +114,7 @@ public PagedResources> getAddressLevelsByOperatingIndivid @RequestParam("lastModifiedDateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) DateTime lastModifiedDateTime, @RequestParam("now") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) DateTime now, Pageable pageable) { - return wrap(scopeBasedSyncService.getSyncResultsByCatchment(locationRepository, userService.getCurrentUser(), lastModifiedDateTime, now, pageable, SyncEntityName.Location)); + return wrap(scopeBasedSyncService.getSyncResultsByCatchment(locationSyncRepository, userService.getCurrentUser(), lastModifiedDateTime, now, pageable, SyncEntityName.Location)); } @PutMapping(value = "/locations/{id}") diff --git a/avni-server-api/src/main/java/org/avni/server/web/LocationMappingController.java b/avni-server-api/src/main/java/org/avni/server/web/LocationMappingController.java index 297391914..3080d789a 100644 --- a/avni-server-api/src/main/java/org/avni/server/web/LocationMappingController.java +++ b/avni-server-api/src/main/java/org/avni/server/web/LocationMappingController.java @@ -1,6 +1,7 @@ package org.avni.server.web; import org.avni.server.dao.LocationMappingRepository; +import org.avni.server.dao.LocationMappingSyncRepository; import org.avni.server.dao.sync.SyncEntityName; import org.avni.server.domain.ParentLocationMapping; import org.avni.server.service.ScopeBasedSyncService; @@ -17,25 +18,30 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import javax.transaction.Transactional; + @RestController public class LocationMappingController implements RestControllerResourceProcessor { private final LocationMappingRepository locationMappingRepository; private final UserService userService; private final ScopeBasedSyncService scopeBasedSyncService; + private final LocationMappingSyncRepository locationMappingSyncRepository; @Autowired - public LocationMappingController(UserService userService, LocationMappingRepository locationMappingRepository, ScopeBasedSyncService scopeBasedSyncService) { + public LocationMappingController(UserService userService, LocationMappingRepository locationMappingRepository, ScopeBasedSyncService scopeBasedSyncService, LocationMappingSyncRepository locationMappingSyncRepository) { this.userService = userService; this.locationMappingRepository = locationMappingRepository; this.scopeBasedSyncService = scopeBasedSyncService; + this.locationMappingSyncRepository = locationMappingSyncRepository; } @RequestMapping(value = {"/locationMapping/search/lastModified", "/locationMapping/search/byCatchmentAndLastModified"}, method = RequestMethod.GET) + @Transactional public PagedResources> getParentLocationMappingsByOperatingIndividualScope( @RequestParam("lastModifiedDateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) DateTime lastModifiedDateTime, @RequestParam("now") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) DateTime now, Pageable pageable) { - return wrap(scopeBasedSyncService.getSyncResultsByCatchment(locationMappingRepository, userService.getCurrentUser(), lastModifiedDateTime, now, pageable, SyncEntityName.LocationMapping)); + return wrap(scopeBasedSyncService.getSyncResultsByCatchment(locationMappingSyncRepository, userService.getCurrentUser(), lastModifiedDateTime, now, pageable, SyncEntityName.LocationMapping)); } @Override