From cfbfc8a57626250b49f9fc8fc037b98c3085f279 Mon Sep 17 00:00:00 2001 From: Holash Chand <132540997+holashchand@users.noreply.github.com> Date: Mon, 27 Jan 2025 12:57:18 +0530 Subject: [PATCH] Added support to search projects based on ancestor project id (#1298) * Added support to search projects based on ancestor ids * changed ancestor ids to a boolean isAncestorProjectId * minor fix * Fixed java heap problem due to no filter in include descendants, include ancestors etc queries * Added support to find root level project * Added missing parameter * Added code comments * incremented version of project service * added change log --- health-services/project/CHANGELOG.md | 3 + health-services/project/pom.xml | 2 +- .../project/repository/ProjectRepository.java | 95 +++++++++++-------- .../ProjectAddressQueryBuilder.java | 22 ++++- .../egov/project/service/ProjectService.java | 22 +++-- .../web/controllers/ProjectApiController.java | 8 +- 6 files changed, 94 insertions(+), 58 deletions(-) diff --git a/health-services/project/CHANGELOG.md b/health-services/project/CHANGELOG.md index 95369041416..c995b81daaf 100644 --- a/health-services/project/CHANGELOG.md +++ b/health-services/project/CHANGELOG.md @@ -1,5 +1,8 @@ All notable changes to this module will be documented in this file. +## 1.1.6 - 2025-01-27 +- Added isAncestorProjectId param for search projects API to support search projects with ancestor project id as well + ## 1.1.5 - 2024-08-07 - Added UserAction functionality with support for Location capture. diff --git a/health-services/project/pom.xml b/health-services/project/pom.xml index 32d7bd8fa3e..2f5e99a45ab 100644 --- a/health-services/project/pom.xml +++ b/health-services/project/pom.xml @@ -5,7 +5,7 @@ project jar project - 1.1.5 + 1.1.6 17 ${java.version} diff --git a/health-services/project/src/main/java/org/egov/project/repository/ProjectRepository.java b/health-services/project/src/main/java/org/egov/project/repository/ProjectRepository.java index ae7b4c9994b..31bd571cb77 100644 --- a/health-services/project/src/main/java/org/egov/project/repository/ProjectRepository.java +++ b/health-services/project/src/main/java/org/egov/project/repository/ProjectRepository.java @@ -69,37 +69,44 @@ public ProjectRepository(Producer producer, NamedParameterJdbcTemplate namedPara } - public List getProjects(ProjectRequest project, Integer limit, Integer offset, String tenantId, Long lastChangedSince, Boolean includeDeleted, Boolean includeAncestors, Boolean includeDescendants, Long createdFrom, Long createdTo) { + /** + * @param isAncestorProjectId When true, treats the project IDs in the ProjectRequest as ancestor project IDs + */ + public List getProjects(ProjectRequest project, Integer limit, Integer offset, String tenantId, Long lastChangedSince, Boolean includeDeleted, Boolean includeAncestors, Boolean includeDescendants, Long createdFrom, Long createdTo, boolean isAncestorProjectId) { //Fetch Projects based on search criteria - List projects = getProjectsBasedOnSearchCriteria(project.getProjects(), limit, offset, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo); + List projects = getProjectsBasedOnSearchCriteria(project.getProjects(), limit, offset, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, isAncestorProjectId); Set projectIds = projects.stream().map(Project :: getId).collect(Collectors.toSet()); List ancestors = null; List descendants = null; - //Get Project ancestors if includeAncestors flag is true - if (includeAncestors) { - ancestors = getProjectAncestors(projects); - if (ancestors != null && !ancestors.isEmpty()) { - List ancestorProjectIds = ancestors.stream().map(Project :: getId).collect(Collectors.toList()); - projectIds.addAll(ancestorProjectIds); + List targets = new ArrayList<>(); + List documents = new ArrayList<>(); + if(!projectIds.isEmpty()) { + //Get Project ancestors if includeAncestors flag is true + if (includeAncestors) { + ancestors = getProjectAncestors(projects); + if (ancestors != null && !ancestors.isEmpty()) { + List ancestorProjectIds = ancestors.stream().map(Project :: getId).collect(Collectors.toList()); + projectIds.addAll(ancestorProjectIds); + } } - } - //Get Project descendants if includeDescendants flag is true - if (includeDescendants) { - descendants = getProjectDescendants(projects); - if (descendants != null && !descendants.isEmpty()) { - List descendantsProjectIds = descendants.stream().map(Project :: getId).collect(Collectors.toList()); - projectIds.addAll(descendantsProjectIds); + //Get Project descendants if includeDescendants flag is true + if (includeDescendants) { + descendants = getProjectDescendants(projects); + if (descendants != null && !descendants.isEmpty()) { + List descendantsProjectIds = descendants.stream().map(Project :: getId).collect(Collectors.toList()); + projectIds.addAll(descendantsProjectIds); + } } - } - //Fetch targets based on Project Ids - List targets = getTargetsBasedOnProjectIds(projectIds); + //Fetch targets based on Project Ids + targets = getTargetsBasedOnProjectIds(projectIds); - //Fetch documents based on Project Ids - List documents = getDocumentsBasedOnProjectIds(projectIds); + //Fetch documents based on Project Ids + documents = getDocumentsBasedOnProjectIds(projectIds); + } //Construct Project Objects with fetched projects, targets and documents using Project id return buildProjectSearchResult(projects, targets, documents, ancestors, descendants); @@ -114,28 +121,32 @@ public List getProjects(@NotNull @Valid ProjectSearch projectSearch, @V List ancestors = null; List descendants = null; - //Get Project ancestors if includeAncestors flag is true - if (urlParams.getIncludeAncestors()) { - ancestors = getProjectAncestors(projects); - if (ancestors != null && !ancestors.isEmpty()) { - List ancestorProjectIds = ancestors.stream().map(Project :: getId).collect(Collectors.toList()); - projectIds.addAll(ancestorProjectIds); + List targets = new ArrayList<>(); + List documents = new ArrayList<>(); + if(!projectIds.isEmpty()) { + //Get Project ancestors if includeAncestors flag is true + if (urlParams.getIncludeAncestors()) { + ancestors = getProjectAncestors(projects); + if (ancestors != null && !ancestors.isEmpty()) { + List ancestorProjectIds = ancestors.stream().map(Project :: getId).toList(); + projectIds.addAll(ancestorProjectIds); + } } - } - //Get Project descendants if includeDescendants flag is true - if (urlParams.getIncludeDescendants()) { - descendants = getProjectDescendants(projects); - if (descendants != null && !descendants.isEmpty()) { - List descendantsProjectIds = descendants.stream().map(Project :: getId).collect(Collectors.toList()); - projectIds.addAll(descendantsProjectIds); + //Get Project descendants if includeDescendants flag is true + if (urlParams.getIncludeDescendants()) { + descendants = getProjectDescendants(projects); + if (descendants != null && !descendants.isEmpty()) { + List descendantsProjectIds = descendants.stream().map(Project :: getId).toList(); + projectIds.addAll(descendantsProjectIds); + } } - } - //Fetch targets based on Project Ids - List targets = getTargetsBasedOnProjectIds(projectIds); + //Fetch targets based on Project Ids + targets = getTargetsBasedOnProjectIds(projectIds); - //Fetch documents based on Project Ids - List documents = getDocumentsBasedOnProjectIds(projectIds); + //Fetch documents based on Project Ids + documents = getDocumentsBasedOnProjectIds(projectIds); + } //Construct Project Objects with fetched projects, targets and documents using Project id return buildProjectSearchResult(projects, targets, documents, ancestors, descendants); @@ -151,9 +162,9 @@ private List getProjectsBasedOnV2SearchCriteria(@NotNull @Valid Project } /* Fetch Projects based on search criteria */ - private List getProjectsBasedOnSearchCriteria(List projectsRequest, Integer limit, Integer offset, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo) { + private List getProjectsBasedOnSearchCriteria(List projectsRequest, Integer limit, Integer offset, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, boolean isAncestorProjectId) { List preparedStmtList = new ArrayList<>(); - String query = queryBuilder.getProjectSearchQuery(projectsRequest, limit, offset, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, preparedStmtList, false); + String query = queryBuilder.getProjectSearchQuery(projectsRequest, limit, offset, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, isAncestorProjectId, preparedStmtList, false); List projects = jdbcTemplate.query(query, addressRowMapper, preparedStmtList.toArray()); log.info("Fetched project list based on given search criteria"); @@ -339,9 +350,9 @@ private void addDescendantsToProjectSearchResult(Project project, List * query build at the run time) * @return */ - public Integer getProjectCount(ProjectRequest project, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo) { + public Integer getProjectCount(ProjectRequest project, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, boolean isAncestorProjectId) { List preparedStatement = new ArrayList<>(); - String query = queryBuilder.getSearchCountQueryString(project.getProjects(), tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, preparedStatement); + String query = queryBuilder.getSearchCountQueryString(project.getProjects(), tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, isAncestorProjectId, preparedStatement); if (query == null) return 0; diff --git a/health-services/project/src/main/java/org/egov/project/repository/querybuilder/ProjectAddressQueryBuilder.java b/health-services/project/src/main/java/org/egov/project/repository/querybuilder/ProjectAddressQueryBuilder.java index 4b3aeda7bac..bce83d7cc5d 100644 --- a/health-services/project/src/main/java/org/egov/project/repository/querybuilder/ProjectAddressQueryBuilder.java +++ b/health-services/project/src/main/java/org/egov/project/repository/querybuilder/ProjectAddressQueryBuilder.java @@ -46,8 +46,11 @@ public class ProjectAddressQueryBuilder { "left join project_address addr " + "on prj.id = addr.projectId ";; - /* Constructs project search query based on conditions */ - public String getProjectSearchQuery(List projects, Integer limit, Integer offset, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, List preparedStmtList, boolean isCountQuery) { + /** + * Constructs project search query based on conditions + * @param isAncestorProjectId if set to true, project id in the projects would be considered as ancestor project id. + */ + public String getProjectSearchQuery(List projects, Integer limit, Integer offset, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, boolean isAncestorProjectId, List preparedStmtList, boolean isCountQuery) { //This uses a ternary operator to choose between PROJECTS_COUNT_QUERY or FETCH_PROJECT_ADDRESS_QUERY based on the value of isCountQuery. String query = isCountQuery ? PROJECTS_COUNT_QUERY : FETCH_PROJECT_ADDRESS_QUERY; StringBuilder queryBuilder = new StringBuilder(query); @@ -69,7 +72,16 @@ public String getProjectSearchQuery(List projects, Integer limit, Integ } } - if (StringUtils.isNotBlank(project.getId())) { + /* + * If isAncestorProjectId is set to true, Then either id equals to project id or projectHierarchy + * should have id of the project + */ + if (isAncestorProjectId && StringUtils.isNotBlank(project.getId())) { + addClauseIfRequired(preparedStmtList, queryBuilder); + queryBuilder.append(" ( prj.projectHierarchy LIKE ? OR prj.id =? ) "); + preparedStmtList.add('%' + project.getId() + '%'); + preparedStmtList.add(project.getId()); + } else if (StringUtils.isNotBlank(project.getId())) { addClauseIfRequired(preparedStmtList, queryBuilder); queryBuilder.append(" prj.id =? "); preparedStmtList.add(project.getId()); @@ -379,8 +391,8 @@ public String getProjectDescendantsSearchQueryBasedOnIds(List projectIds } /* Returns query to get total projects count based on project search params */ - public String getSearchCountQueryString(List projects, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, List preparedStatement) { - String query = getProjectSearchQuery(projects, config.getMaxLimit(), config.getDefaultOffset(), tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, preparedStatement, true); + public String getSearchCountQueryString(List projects, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, boolean isAncestorProjectId, List preparedStatement) { + String query = getProjectSearchQuery(projects, config.getMaxLimit(), config.getDefaultOffset(), tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, isAncestorProjectId, preparedStatement, true); return query; } diff --git a/health-services/project/src/main/java/org/egov/project/service/ProjectService.java b/health-services/project/src/main/java/org/egov/project/service/ProjectService.java index e27c827ce29..349248e1ac6 100644 --- a/health-services/project/src/main/java/org/egov/project/service/ProjectService.java +++ b/health-services/project/src/main/java/org/egov/project/service/ProjectService.java @@ -79,6 +79,11 @@ public ProjectRequest createProject(ProjectRequest projectRequest) { return projectRequest; } + /** + * Search for projects based on various criteria + * @param isAncestorProjectId When true, treats the project IDs in the search criteria as ancestor project IDs + * and returns all projects (including children) under these ancestors + */ public List searchProject( ProjectRequest project, Integer limit, @@ -89,7 +94,8 @@ public List searchProject( Boolean includeAncestors, Boolean includeDescendants, Long createdFrom, - Long createdTo + Long createdTo, + boolean isAncestorProjectId ) { projectValidator.validateSearchProjectRequest(project, limit, offset, tenantId, createdFrom, createdTo); List projects = projectRepository.getProjects( @@ -102,7 +108,8 @@ public List searchProject( includeAncestors, includeDescendants, createdFrom, - createdTo + createdTo, + isAncestorProjectId ); return projects; } @@ -125,7 +132,7 @@ public ProjectRequest updateProject(ProjectRequest request) { List projectsFromDB = searchProject( getSearchProjectRequest(request.getProjects(), request.getRequestInfo(), false), projectConfiguration.getMaxLimit(), projectConfiguration.getDefaultOffset(), - request.getProjects().get(0).getTenantId(), null, false, false, false, null, null + request.getProjects().get(0).getTenantId(), null, false, false, false, null, null, false ); log.info("Fetched projects for update request"); @@ -280,7 +287,8 @@ private void checkAndEnrichCascadingProjectDates(ProjectRequest request, Project true, true, null, - null + null, + false ); /* @@ -301,7 +309,7 @@ private List getParentProjects(ProjectRequest projectRequest) { List parentProjects = null; List projectsForSearchRequest = projectRequest.getProjects().stream().filter(p -> StringUtils.isNotBlank(p.getParent())).collect(Collectors.toList()); if (projectsForSearchRequest.size() > 0) { - parentProjects = searchProject(getSearchProjectRequest(projectsForSearchRequest, projectRequest.getRequestInfo(), true), projectConfiguration.getMaxLimit(), projectConfiguration.getDefaultOffset(), projectRequest.getProjects().get(0).getTenantId(), null, false, false, false, null, null); + parentProjects = searchProject(getSearchProjectRequest(projectsForSearchRequest, projectRequest.getRequestInfo(), true), projectConfiguration.getMaxLimit(), projectConfiguration.getDefaultOffset(), projectRequest.getProjects().get(0).getTenantId(), null, false, false, false, null, null, false); } log.info("Fetched parent projects from DB"); return parentProjects; @@ -329,8 +337,8 @@ private ProjectRequest getSearchProjectRequest(List projects, RequestIn /** * @return Count of List of matching projects */ - public Integer countAllProjects(ProjectRequest project, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo) { - return projectRepository.getProjectCount(project, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo); + public Integer countAllProjects(ProjectRequest project, String tenantId, Long lastChangedSince, Boolean includeDeleted, Long createdFrom, Long createdTo, boolean isAncestorProjectId) { + return projectRepository.getProjectCount(project, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, isAncestorProjectId); } diff --git a/health-services/project/src/main/java/org/egov/project/web/controllers/ProjectApiController.java b/health-services/project/src/main/java/org/egov/project/web/controllers/ProjectApiController.java index 2ec0adc34d3..49017853794 100644 --- a/health-services/project/src/main/java/org/egov/project/web/controllers/ProjectApiController.java +++ b/health-services/project/src/main/java/org/egov/project/web/controllers/ProjectApiController.java @@ -486,7 +486,8 @@ public ResponseEntity searchProject( @ApiParam(value = "Used in project search API to specify if response should include project elements that are in the preceding hierarchy of matched projects.", defaultValue = "false") @Valid @RequestParam(value = "includeAncestors", required = false, defaultValue = "false") Boolean includeAncestors, @ApiParam(value = "Used in project search API to specify if response should include project elements that are in the following hierarchy of matched projects.", defaultValue = "false") @Valid @RequestParam(value = "includeDescendants", required = false, defaultValue = "false") Boolean includeDescendants, @ApiParam(value = "Used in project search API to limit the search results to only those projects whose creation date is after the specified 'createdFrom' date", defaultValue = "false") @Valid @RequestParam(value = "createdFrom", required = false) Long createdFrom, - @ApiParam(value = "Used in project search API to limit the search results to only those projects whose creation date is before the specified 'createdTo' date", defaultValue = "false") @Valid @RequestParam(value = "createdTo", required = false) Long createdTo + @ApiParam(value = "Used in project search API to limit the search results to only those projects whose creation date is before the specified 'createdTo' date", defaultValue = "false") @Valid @RequestParam(value = "createdTo", required = false) Long createdTo, + @ApiParam(value = "Used in project search API to specify if response should be one which is in the preceding hierarchy of matched projects.") @Valid @RequestParam(value = "isAncestorProjectId", required = false, defaultValue = "false") boolean isAncestorProjectId ) { List projects = projectService.searchProject( project, @@ -498,10 +499,11 @@ public ResponseEntity searchProject( includeAncestors, includeDescendants, createdFrom, - createdTo + createdTo, + isAncestorProjectId ); ResponseInfo responseInfo = ResponseInfoFactory.createResponseInfo(project.getRequestInfo(), true); - Integer count = projectService.countAllProjects(project, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo); + Integer count = projectService.countAllProjects(project, tenantId, lastChangedSince, includeDeleted, createdFrom, createdTo, isAncestorProjectId); ProjectResponse projectResponse = ProjectResponse.builder().responseInfo(responseInfo).project(projects).totalCount(count).build(); return new ResponseEntity(projectResponse, HttpStatus.OK); }