Skip to content

Commit

Permalink
#716 | Reset role and ensure organisation when syncing for Location a…
Browse files Browse the repository at this point in the history
…nd LocationMapping
  • Loading branch information
vinayvenu committed Apr 17, 2024
1 parent 319e5d7 commit f43f14e
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,22 @@
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;
import java.util.List;

@Repository
@Transactional
public class EncounterSearchRepository {
public class EncounterSearchRepository extends RoleSwitchableRepository{

@PersistenceContext
private EntityManager entityManager;
public EncounterSearchRepository(EntityManager entityManager) {
super(entityManager);
}

@Transactional
public List<Encounter> search(EncounterSearchRequest searchRequest) {
Expand All @@ -38,16 +37,6 @@ public List<Encounter> 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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,12 +23,14 @@ public interface LocationMappingRepository extends ReferenceDataRepository<Paren
" inner join address_level al1 on al.lineage @> 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<ParentLocationMapping> getSyncResults(
long catchmentId,
Date lastModifiedDateTime,
Date now,
long organisationId,
Pageable pageable
);

Expand All @@ -43,7 +46,8 @@ Page<ParentLocationMapping> getSyncResults(

@Override
default Page<ParentLocationMapping> 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ParentLocationMapping> {
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<ParentLocationMapping> getSyncResultsAsSlice(SyncParameters syncParameters) {
return locationMappingRepository.getSyncResultsAsSlice(syncParameters);
}


@Override
@Transactional
public Page<ParentLocationMapping> getSyncResults(SyncParameters syncParameters) {
try {
setRoleToNone();
Page<ParentLocationMapping> syncResults = locationMappingRepository.getSyncResults(syncParameters);
return syncResults;
} finally {
setRoleBackToUser();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ public interface LocationRepository extends ReferenceDataRepository<AddressLevel
" inner join address_level al on cam.addresslevel_id = al.id\n" +
" inner join address_level al1 on al.lineage @> 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<AddressLevel> getSyncResults(long catchmentId, Date lastModifiedDateTime, Date now, Pageable pageable);
Page<AddressLevel> getSyncResults(long catchmentId, Date lastModifiedDateTime, Date now, long organisationId, Pageable pageable);

@Query(value = "select count(*)\n" +
"from catchment c\n" +
Expand Down Expand Up @@ -105,7 +106,8 @@ public interface LocationRepository extends ReferenceDataRepository<AddressLevel

@Override
default Page<AddressLevel> 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AddressLevel> {
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<AddressLevel> getSyncResultsAsSlice(SyncParameters syncParameters) {
return locationRepository.getSyncResultsAsSlice(syncParameters);
}

@Override
@Transactional
public Page<AddressLevel> getSyncResults(SyncParameters syncParameters) {
try {
setRoleToNone();
Page<AddressLevel> syncResults = locationRepository.getSyncResults(syncParameters);
return syncResults;
} finally {
setRoleBackToUser();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

@SuppressWarnings("rawtypes")
@NoRepositoryBean
public interface OperatingIndividualScopeAwareRepository<T extends CHSEntity> extends JpaSpecificationExecutor<T>, CustomCHSJpaRepository<T, Long> {
public interface OperatingIndividualScopeAwareRepository<T extends CHSEntity> extends JpaSpecificationExecutor<T>, CustomCHSJpaRepository<T, Long>, SyncableRepository<T> {
default Specification getSpecification(SyncParameters syncParameters) {
Specification specification;
if (syncParameters.isModificationCheckOnEntity()) {
Expand All @@ -35,16 +35,19 @@ default Specification getSpecification(SyncParameters syncParameters) {
return specification;
}

@Override
default Slice<T> getSyncResultsAsSlice(SyncParameters syncParameters) {
Specification specification = getSpecification(syncParameters);
return findAllAsSlice(specification, syncParameters.getPageable());
}

@Override
default Page<T> getSyncResults(SyncParameters syncParameters) {
Specification specification = getSpecification(syncParameters);
return findAll(specification, syncParameters.getPageable());
}

@Override
boolean isEntityChanged(SyncParameters syncParameters);

default Specification<T> getAuditSpecification(SyncParameters syncParameters) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.avni.server.dao;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Slice;

public interface SyncableRepository<T> {
Page<T> getSyncResults(SyncParameters syncParameters);
boolean isEntityChanged(SyncParameters syncParameters);
Slice<T> getSyncResultsAsSlice(SyncParameters syncParameters);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<T> repository();
SyncableRepository<T> repository();

boolean isScopeEntityChanged(DateTime lastModifiedDateTime, String typeUUID);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -22,31 +22,31 @@ public ScopeBasedSyncService(AddressLevelService addressLevelService) {
this.addressLevelService = addressLevelService;
}

public Page<T> getSyncResultsBySubjectTypeRegistrationLocation(OperatingIndividualScopeAwareRepository<T> repository, User user, DateTime lastModifiedDateTime, DateTime now, Long typeId, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) {
public Page<T> getSyncResultsBySubjectTypeRegistrationLocation(SyncableRepository<T> repository, User user, DateTime lastModifiedDateTime, DateTime now, Long typeId, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) {
List<Long> 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<T> getSyncResultsBySubjectTypeRegistrationLocation(OperatingIndividualScopeAwareRepository<T> repository, User user, DateTime lastModifiedDateTime, DateTime now, String entityTypeUuid, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) {
public Page<T> getSyncResultsBySubjectTypeRegistrationLocation(SyncableRepository<T> repository, User user, DateTime lastModifiedDateTime, DateTime now, String entityTypeUuid, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) {
List<Long> 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<T> getSyncResultsByCatchment(OperatingIndividualScopeAwareRepository<T> repository, User user, DateTime lastModifiedDateTime, DateTime now, Pageable pageable, SyncEntityName syncEntityName) {
public Page<T> getSyncResultsByCatchment(SyncableRepository<T> 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<T> getSyncResultsBySubjectTypeRegistrationLocationAsSlice(OperatingIndividualScopeAwareRepository<T> repository, User user, DateTime lastModifiedDateTime, DateTime now, Long typeId, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) {
public Slice<T> getSyncResultsBySubjectTypeRegistrationLocationAsSlice(SyncableRepository<T> repository, User user, DateTime lastModifiedDateTime, DateTime now, Long typeId, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) {
List<Long> 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<T> getSyncResultsBySubjectTypeRegistrationLocationAsSlice(OperatingIndividualScopeAwareRepository<T> repository, User user, DateTime lastModifiedDateTime, DateTime now, String entityTypeUuid, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) {
public Slice<T> getSyncResultsBySubjectTypeRegistrationLocationAsSlice(SyncableRepository<T> repository, User user, DateTime lastModifiedDateTime, DateTime now, String entityTypeUuid, Pageable pageable, SubjectType subjectType, SyncEntityName syncEntityName) {
List<Long> 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<T> getSyncResultsByCatchmentAsSlice(OperatingIndividualScopeAwareRepository<T> repository, User user, DateTime lastModifiedDateTime, DateTime now, Pageable pageable, SyncEntityName syncEntityName) {
public Slice<T> getSyncResultsByCatchmentAsSlice(SyncableRepository<T> 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()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -45,14 +46,16 @@ public class LocationController implements RestControllerResourceProcessor<Addre
private final LocationService locationService;
private final ScopeBasedSyncService<AddressLevel> scopeBasedSyncService;
private final AccessControlService accessControlService;
private LocationSyncRepository locationSyncRepository;

@Autowired
public LocationController(LocationRepository locationRepository, UserService userService, LocationService locationService, ScopeBasedSyncService<AddressLevel> scopeBasedSyncService, AccessControlService accessControlService) {
public LocationController(LocationRepository locationRepository, UserService userService, LocationService locationService, ScopeBasedSyncService<AddressLevel> 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());
}

Expand Down Expand Up @@ -111,7 +114,7 @@ public PagedResources<Resource<AddressLevel>> 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}")
Expand Down
Loading

0 comments on commit f43f14e

Please sign in to comment.