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 0669aa305..adbd67de4 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 @@ -18,17 +18,19 @@ import javax.persistence.QueryHint; import javax.validation.constraints.NotNull; -import java.util.*; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Optional; @Repository @RepositoryRestResource(collectionResourceRel = "locations", path = "locations") public interface LocationRepository extends ReferenceDataRepository, FindByLastModifiedDateTime, OperatingIndividualScopeAwareRepository { @Query(value = "select al.id, al.uuid, title, type_id as typeId, alt.name as typeString, al.parent_id as parentId, " + - "cast(lineage as text) as lineage, title_lineage as titleLineage, alt.level " + + "cast(lineage as text) as lineage, null as titleLineage, alt.level " + "from address_level al " + "left join address_level_type alt on alt.id = al.type_id " + - "left join title_lineage_locations_view tll on tll.lowestpoint_id = al.id " + "where al.id in (:ids)", nativeQuery = true) List findByIdIn(Long[] ids); @@ -57,10 +59,9 @@ public interface LocationRepository extends ReferenceDataRepository findByTitleLineageIgnoreCase(String locationTitle String getTitleLineageById(Long addressId); @Query(value = "select al.id, al.uuid, title, type_id as typeId, alt.name as typeString, al.parent_id as parentId, " + - "cast(lineage as text) as lineage, title_lineage as titleLineage, alt.level " + + "cast(lineage as text) as lineage, " + + "null as titleLineage, " + + "alt.level " + "from address_level al " + "left join address_level_type alt on alt.id = al.type_id " + - "left join title_lineage_locations_view tll on tll.lowestpoint_id = al.id " + "where al.is_voided = false", nativeQuery = true) Page findNonVoidedLocations(Pageable pageable); @Query(value = "select al.id, al.uuid, title, type_id as typeId, alt.name as typeString, al.parent_id as parentId, " + - "cast(lineage as text) as lineage, title_lineage as titleLineage, alt.level " + + "cast(lineage as text) as lineage, null as titleLineage, alt.level " + "from address_level al " + "left join address_level_type alt on alt.id = al.type_id " + - "left join title_lineage_locations_view tll on tll.lowestpoint_id = al.id " + "where al.is_voided = false " + "and al.type_id = :typeId " + "order by al.title ", @@ -226,20 +227,18 @@ default Optional findByTitleLineageIgnoreCase(String locationTitle List findNonVoidedLocationsByTypeId(Long typeId); @Query(value = "select al.id, al.uuid, title, type_id as typeId, alt.name as typeString, al.parent_id as parentId, " + - "cast(lineage as text) as lineage, title_lineage as titleLineage, alt.level " + + "cast(lineage as text) as lineage, null as titleLineage, alt.level " + "from address_level al " + "left join address_level_type alt on alt.id = al.type_id " + - "left join title_lineage_locations_view tll on tll.lowestpoint_id = al.id " + "where al.is_voided = false " + "and al.uuid = :uuid ", nativeQuery = true) LocationProjection findNonVoidedLocationsByUuid(String uuid); @Query(value = "select al.id, al.uuid, title, type_id as typeId, alt.name as typeString, al.parent_id as parentId,\n" + - "cast(lineage as text) as lineage, title_lineage as titleLineage, alt.level " + + "cast(lineage as text) as lineage, null as titleLineage, alt.level " + "from address_level al\n" + "left join address_level_type alt on alt.id = al.type_id\n" + - "left join title_lineage_locations_view tll on tll.lowestpoint_id = al.id " + "where lineage @>" + " (select lineage" + " from address_level" + @@ -248,10 +247,9 @@ default Optional findByTitleLineageIgnoreCase(String locationTitle List getParentsWithMaxLevelTypeId(String uuid, Long maxLevelTypeId); @Query(value = "select al.id, al.uuid, title, type_id as typeId, alt.name as typeString, al.parent_id as parentId,\n" + - "cast(lineage as text) as lineage, title_lineage as titleLineage, alt.level " + + "cast(lineage as text) as lineage, null as titleLineage, alt.level " + "from address_level al\n" + "left join address_level_type alt on alt.id = al.type_id\n" + - "left join title_lineage_locations_view tll on tll.lowestpoint_id = al.id " + "where lineage @>" + " (select lineage" + " from address_level" + diff --git a/avni-server-api/src/main/java/org/avni/server/service/AddressLevelService.java b/avni-server-api/src/main/java/org/avni/server/service/AddressLevelService.java index 2fe04d147..f58bde08d 100644 --- a/avni-server-api/src/main/java/org/avni/server/service/AddressLevelService.java +++ b/avni-server-api/src/main/java/org/avni/server/service/AddressLevelService.java @@ -3,13 +3,17 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.avni.server.application.KeyType; +import org.avni.server.application.projections.LocationProjection; import org.avni.server.application.projections.VirtualCatchmentProjection; import org.avni.server.dao.AddressLevelTypeRepository; import org.avni.server.dao.LocationRepository; import org.avni.server.domain.*; import org.avni.server.util.ObjectMapperSingleton; import org.avni.server.web.request.AddressLevelContract; +import org.avni.server.web.request.AddressLevelContractWeb; import org.avni.server.web.request.webapp.SubjectTypeSetting; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.stereotype.Service; import java.util.*; @@ -98,6 +102,20 @@ public String getTitleLineage(AddressLevel location) { return locationRepository.getTitleLineageById(location.getId()); } + public List addTitleLineageToLocation(List locationProjections) { + Map titleLineages = getTitleLineages(locationProjections.stream().map(LocationProjection::getId).collect(Collectors.toList())); + return locationProjections.stream().map(locationProjection -> { + AddressLevelContractWeb addressLevel = AddressLevelContractWeb.fromEntity(locationProjection); + addressLevel.setTitleLineage(titleLineages.get(locationProjection.getId())); + return addressLevel; + }).collect(Collectors.toList()); + } + + public Page addTitleLineageToLocation(Page locationProjections) { + List locationWebContracts = addTitleLineageToLocation(locationProjections.getContent()); + return new PageImpl<>(locationWebContracts, locationProjections.getPageable(), locationProjections.getTotalElements()); + } + // This method uses in memory approach instead of database, because for smaller number of addresses the query plan to achive this is expensive due to over estimation by postgres. public Map getTitleLineages(List addressIds) { List addresses = locationRepository.findAllByIdIn(addressIds); 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 4978cc9c6..d3f3b4253 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 @@ -8,6 +8,7 @@ import org.avni.server.dao.sync.SyncEntityName; import org.avni.server.domain.AddressLevel; import org.avni.server.domain.accessControl.PrivilegeType; +import org.avni.server.service.AddressLevelService; import org.avni.server.service.LocationService; import org.avni.server.service.ScopeBasedSyncService; import org.avni.server.service.UserService; @@ -34,8 +35,8 @@ import javax.transaction.Transactional; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; @RepositoryRestController public class LocationController implements RestControllerResourceProcessor { @@ -47,15 +48,17 @@ public class LocationController implements RestControllerResourceProcessor scopeBasedSyncService; private final AccessControlService accessControlService; private final LocationSyncRepository locationSyncRepository; + private final AddressLevelService addressLevelService; @Autowired - public LocationController(LocationRepository locationRepository, UserService userService, LocationService locationService, ScopeBasedSyncService scopeBasedSyncService, AccessControlService accessControlService, LocationSyncRepository locationSyncRepository) { + public LocationController(LocationRepository locationRepository, UserService userService, LocationService locationService, ScopeBasedSyncService scopeBasedSyncService, AccessControlService accessControlService, LocationSyncRepository locationSyncRepository, AddressLevelService addressLevelService) { this.locationRepository = locationRepository; this.userService = userService; this.locationService = locationService; this.scopeBasedSyncService = scopeBasedSyncService; this.accessControlService = accessControlService; this.locationSyncRepository = locationSyncRepository; + this.addressLevelService = addressLevelService; this.logger = LoggerFactory.getLogger(this.getClass()); } @@ -77,18 +80,18 @@ public ResponseEntity save(@RequestBody List locationContra @GetMapping(value = "/locations") @ResponseBody - public Page getAll(Pageable pageable) { - return locationRepository.findNonVoidedLocations(pageable); + public Page getAll(Pageable pageable) { + return addressLevelService.addTitleLineageToLocation(locationRepository.findNonVoidedLocations(pageable)); } @GetMapping(value = "locations/search/find") @ResponseBody - public Page find( + public Page find( @RequestParam(value = "title", defaultValue = "") String title, @RequestParam(value = "typeId", required = false) Integer typeId, @RequestParam(value = "parentId", required = false) Integer parentId, Pageable pageable) { - return locationService.find(new LocationSearchRequest(title, typeId, parentId, pageable)); + return addressLevelService.addTitleLineageToLocation(locationService.find(new LocationSearchRequest(title, typeId, parentId, pageable))); } @GetMapping(value = "locations/search/findAsList") @@ -101,11 +104,11 @@ public List findAsList( @GetMapping(value = "/locations/search/findAllById") @ResponseBody - public List findByIdIn(@Param("ids") Long[] ids) { + public List findByIdIn(@Param("ids") Long[] ids) { if (ids == null || ids.length == 0) { return new ArrayList<>(); } - return locationRepository.findByIdIn(ids); + return addressLevelService.addTitleLineageToLocation(locationRepository.findByIdIn(ids)); } @RequestMapping(value = {"/locations/search/lastModified", "/locations/search/byCatchmentAndLastModified"}, method = RequestMethod.GET) @@ -157,27 +160,27 @@ public ResponseEntity voidLocation(@PathVariable("id") Long id) { @GetMapping(value = "/locations/search/typeId/{typeId}") @ResponseBody public List getLocationsByTypeId(@PathVariable("typeId") Long typeId) { + //TODO API does not appear to be in use. Remove. accessControlService.checkPrivilege(PrivilegeType.EditLocation); - return locationRepository.findNonVoidedLocationsByTypeId(typeId).stream() - .map(AddressLevelContractWeb::fromEntity) - .collect(Collectors.toList()); + return addressLevelService.addTitleLineageToLocation(locationRepository.findNonVoidedLocationsByTypeId(typeId)); } @GetMapping(value = "locations/parents/{uuid}") @ResponseBody - public List getParents(@PathVariable("uuid") String uuid, + public List getParents(@PathVariable("uuid") String uuid, @RequestParam(value = "maxLevelTypeId", required = false) Long maxLevelTypeId) { - return locationService.getParents(uuid, maxLevelTypeId); + return addressLevelService.addTitleLineageToLocation(locationService.getParents(uuid, maxLevelTypeId)); } @GetMapping(value = "/locations/web") @ResponseBody - public ResponseEntity getLocationByParam(@RequestParam("uuid") String uuid) { + public ResponseEntity getLocationByParam(@RequestParam("uuid") String uuid) { LocationProjection addressLevel = locationRepository.findNonVoidedLocationsByUuid(uuid); if (addressLevel == null) { return ResponseEntity.notFound().build(); } - return new ResponseEntity<>(AddressLevelContractWeb.fromEntity(addressLevel), HttpStatus.OK); + AddressLevelContractWeb addressLevelContract = addressLevelService.addTitleLineageToLocation(Collections.singletonList(addressLevel)).get(0); + return new ResponseEntity<>(addressLevelContract, HttpStatus.OK); } } diff --git a/avni-server-api/src/main/java/org/avni/server/web/request/AddressLevelContractWeb.java b/avni-server-api/src/main/java/org/avni/server/web/request/AddressLevelContractWeb.java index 4950606a2..a487f41e7 100644 --- a/avni-server-api/src/main/java/org/avni/server/web/request/AddressLevelContractWeb.java +++ b/avni-server-api/src/main/java/org/avni/server/web/request/AddressLevelContractWeb.java @@ -7,7 +7,9 @@ public class AddressLevelContractWeb { private Long id; private String name; + private String title; // set to same value as name to handle different consumers of contract private String type; + private String typeString; // set to same value as type to handle different consumers of contract private Long typeId; private Double level; private String lineage; @@ -19,7 +21,9 @@ public static AddressLevelContractWeb fromEntity(AddressLevel addressLevel) { AddressLevelContractWeb addressLevelContractWeb = new AddressLevelContractWeb(); addressLevelContractWeb.setId(addressLevel.getId()); addressLevelContractWeb.setName(addressLevel.getTitle()); - addressLevelContractWeb.setType(addressLevel.getType().getName()); + addressLevelContractWeb.setTitle(addressLevel.getTitle()); + addressLevelContractWeb.setType(addressLevel.getTypeString()); + addressLevelContractWeb.setTypeString(addressLevel.getTypeString()); addressLevelContractWeb.setTypeId(addressLevel.getType().getId()); addressLevelContractWeb.setLevel(addressLevel.getLevel()); addressLevelContractWeb.setParentId(addressLevel.getParentId()); @@ -32,7 +36,9 @@ public static AddressLevelContractWeb fromEntity(LocationProjection locationProj AddressLevelContractWeb addressLevelContractWeb = new AddressLevelContractWeb(); addressLevelContractWeb.setId(locationProjection.getId()); addressLevelContractWeb.setName(locationProjection.getTitle()); + addressLevelContractWeb.setTitle(locationProjection.getTitle()); addressLevelContractWeb.setType(locationProjection.getTypeString()); + addressLevelContractWeb.setTypeString(locationProjection.getTypeString()); addressLevelContractWeb.setTypeId(locationProjection.getTypeId()); addressLevelContractWeb.setLevel(locationProjection.getLevel()); addressLevelContractWeb.setParentId(locationProjection.getParentId()); @@ -113,4 +119,20 @@ public Long getTypeId() { public void setTypeId(Long typeId) { this.typeId = typeId; } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getTypeString() { + return typeString; + } + + public void setTypeString(String typeString) { + this.typeString = typeString; + } }