Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.egov.project.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Duration;
import java.util.Collections;
import org.egov.common.contract.models.AuditDetails;
import jakarta.validation.Valid;
import java.util.Map;
Expand All @@ -19,6 +21,7 @@
import org.egov.project.validator.project.ProjectValidator;
import org.egov.tracer.model.CustomException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
Expand All @@ -44,18 +47,22 @@ public class ProjectService {

private final ObjectMapper objectMapper;

@Autowired
public ProjectService(
ProjectRepository projectRepository,
ProjectValidator projectValidator, ProjectEnrichment projectEnrichment, ProjectConfiguration projectConfiguration, Producer producer,ProjectServiceUtil projectServiceUtil) {
this.projectRepository = projectRepository;
this.projectValidator = projectValidator;
this.projectEnrichment = projectEnrichment;
this.projectConfiguration = projectConfiguration;
this.producer = producer;
this.projectServiceUtil = projectServiceUtil;
this.objectMapper = new ObjectMapper();
}
private final RedisTemplate<String, String> redisTemplate;


@Autowired
public ProjectService(
ProjectRepository projectRepository,
ProjectValidator projectValidator, ProjectEnrichment projectEnrichment, ProjectConfiguration projectConfiguration, Producer producer, ProjectServiceUtil projectServiceUtil, RedisTemplate redisTemplate) {
this.projectRepository = projectRepository;
this.projectValidator = projectValidator;
this.projectEnrichment = projectEnrichment;
this.projectConfiguration = projectConfiguration;
this.producer = producer;
this.projectServiceUtil = projectServiceUtil;
this.objectMapper = new ObjectMapper();
this.redisTemplate = redisTemplate;
}

public List<String> validateProjectIds(List<String> productIds) {
return projectRepository.validateIds(productIds, "id");
Expand All @@ -65,20 +72,39 @@ public List<Project> findByIds(List<String> projectIds){
return projectRepository.findById(projectIds);
}

public ProjectRequest createProject(ProjectRequest projectRequest) {
projectValidator.validateCreateProjectRequest(projectRequest);
//Get parent projects if "parent" is present (For enrichment of projectHierarchy)
List<Project> parentProjects = getParentProjects(projectRequest);
//Validate Parent in request against projects fetched form database
if (parentProjects != null)
projectValidator.validateParentAgainstDB(projectRequest.getProjects(), parentProjects);
projectEnrichment.enrichProjectOnCreate(projectRequest, parentProjects);
log.info("Enriched with Project Number, Ids and AuditDetails");
producer.push(projectConfiguration.getSaveProjectTopic(), projectRequest);
log.info("Pushed to kafka");
return projectRequest;
public ProjectRequest createProject(ProjectRequest projectRequest) {
projectValidator.validateCreateProjectRequest(projectRequest);
//Get parent projects if "parent" is present (For enrichment of projectHierarchy)
List<Project> parentProjects = getParentProjects(projectRequest);
//Validate Parent in request against projects fetched form database
if (parentProjects != null) {
projectValidator.validateParentAgainstDB(projectRequest.getProjects(), parentProjects);
}
projectEnrichment.enrichProjectOnCreate(projectRequest, parentProjects);
log.info("Enriched with Project Number, Ids and AuditDetails");
producer.push(projectConfiguration.getSaveProjectTopic(), projectRequest);
log.info("Pushed to kafka");

// ✅ Save project IDs in Redis after Kafka push
for (Project project : projectRequest.getProjects()) {
String redisKey = "project-create-cache-" + project.getId();

if (StringUtils.isNotBlank(project.getProjectHierarchy())) {
try {
redisTemplate.opsForValue()
.set(redisKey, project.getProjectHierarchy(), Duration.ofDays(1));
log.info("Cached projectHierarchy for project {} in Redis", project.getId());
} catch (Exception ex) {
log.error("Failed to cache projectHierarchy for project {}: {}", project.getId(), ex.getMessage(), ex);
}
} else {
log.warn("ProjectHierarchy is blank for project {}, not caching in Redis", project.getId());
}
}

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
Expand Down Expand Up @@ -304,17 +330,75 @@ private void checkAndEnrichCascadingProjectDates(ProjectRequest request, Project
}


/* Search for parent projects based on "parent" field and returns parent projects */
private List<Project> getParentProjects(ProjectRequest projectRequest) {
List<Project> parentProjects = null;
List<Project> 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, false);
/* Search for parent projects based on "parent" field and returns parent projects */
private List<Project> getParentProjects(ProjectRequest projectRequest) {
List<Project> parentProjects = new ArrayList<>();

List<Project> projectsWithParent = projectRequest.getProjects().stream()
.filter(p -> StringUtils.isNotBlank(p.getParent()))
.collect(Collectors.toList());

if (projectsWithParent.isEmpty()) {
return Collections.emptyList();
}

List<String> missingParentIds = new ArrayList<>();

for (Project project : projectsWithParent) {
String parentId = project.getParent();
String redisKey = "project-create-cache-" + parentId;

try {
String cachedHierarchy = redisTemplate.opsForValue().get(redisKey);
if (StringUtils.isNotBlank(cachedHierarchy)) {
log.info("Parent project hierarchy for {} fetched from Redis", parentId);

Project parent = new Project();
parent.setId(parentId);
parent.setProjectHierarchy(cachedHierarchy);
parentProjects.add(parent);
} else {
log.info("No hierarchy found in Redis for parent {}", parentId);
missingParentIds.add(parentId);
}
log.info("Fetched parent projects from DB");
return parentProjects;
} catch (Exception ex) {
log.error("Redis error while fetching hierarchy for parent {}: {}", parentId, ex.getMessage(), ex);
missingParentIds.add(parentId); // fallback to DB or ignore
}
}

if (!missingParentIds.isEmpty()) {
List<Project> dbQueryProjects = missingParentIds.stream()
.map(id -> Project.builder().id(id)
.tenantId(projectRequest.getProjects().get(0).getTenantId()).build())
.collect(Collectors.toList());

ProjectRequest searchRequest = getSearchProjectRequest(dbQueryProjects,
projectRequest.getRequestInfo(), false);

List<Project> dbProjects = searchProject(
searchRequest,
projectConfiguration.getMaxLimit(),
projectConfiguration.getDefaultOffset(),
searchRequest.getProjects().get(0).getTenantId(),
null, false, false, false, null, null, false
);

for (Project parent : dbProjects) {
String redisKey = "project-create-cache-" + parent.getId();
try {
redisTemplate.opsForValue().set(redisKey, parent.getProjectHierarchy(), Duration.ofDays(1));
} catch (Exception ex) {
log.error("Redis error while setting hierarchy for project {}: {}", parent.getId(), ex.getMessage(), ex);
}
parentProjects.add(parent);
}

}

return parentProjects;
}

/* Construct Project Request object for search which contains project id and tenantId */
private ProjectRequest getSearchProjectRequest(List<Project> projects, RequestInfo requestInfo, Boolean isParentProjectSearch) {
List<Project> projectList = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.egov.project.repository.ProjectRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
Expand All @@ -32,10 +33,13 @@
public class PfProjectIdValidator implements Validator<ProjectFacilityBulkRequest, ProjectFacility> {

private final ProjectRepository projectRepository;
private final RedisTemplate<String,String> redisTemplate;


@Autowired
public PfProjectIdValidator(ProjectRepository projectRepository) {
public PfProjectIdValidator(ProjectRepository projectRepository,RedisTemplate redisTemplate) {
this.projectRepository = projectRepository;
this.redisTemplate = redisTemplate;
}


Expand All @@ -52,9 +56,28 @@ public Map<ProjectFacility, List<Error>> validate(ProjectFacilityBulkRequest req
Map<String, ProjectFacility> eMap = getIdToObjMap(validEntities, idMethod);
if (!eMap.isEmpty()) {
List<String> entityIds = new ArrayList<>(eMap.keySet());
List<String> existingProjectIds = projectRepository.validateIds(entityIds,
List<String> existingProjectIds = new ArrayList<>();
List<String> cacheMissIds = new ArrayList<>();

for (String id : entityIds) {
String redisKey = "project-create-cache-" + id;
try {
if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) {
existingProjectIds.add(id);
} else {
cacheMissIds.add(id);
}
} catch (Exception ex) {
log.error("Redis error while checking key: {}", redisKey, ex);
cacheMissIds.add(id); // safely fallback to DB
}
}
if (!cacheMissIds.isEmpty()) {
List<String> dbValidIds = projectRepository.validateIds(cacheMissIds,
getIdFieldName(idMethod));
List<ProjectFacility> invalidEntities = validEntities.stream().filter(notHavingErrors()).filter(entity ->
existingProjectIds.addAll(dbValidIds);
}
Comment on lines +59 to +79
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider extracting common Redis caching logic.

The Redis caching logic here is nearly identical to PsProjectIdValidator. Consider creating a shared utility class or base validator to reduce code duplication.

Example approach:

  1. Create a RedisProjectCacheUtil class with methods like getCachedProjectIds() and cacheProjectIds()
  2. Or create an abstract base validator class with the common caching logic
  3. This would ensure consistent caching behavior and make maintenance easier
🤖 Prompt for AI Agents
In
health-services/project/src/main/java/org/egov/project/validator/facility/PfProjectIdValidator.java
around lines 59 to 79, the Redis caching logic is duplicated and similar to that
in PsProjectIdValidator. To fix this, extract the common Redis caching code into
a shared utility class such as RedisProjectCacheUtil with methods like
getCachedProjectIds() and cacheProjectIds(), or create an abstract base
validator class encapsulating this logic. Then refactor both validators to use
this shared code, reducing duplication and improving maintainability.

List<ProjectFacility> invalidEntities = validEntities.stream().filter(notHavingErrors()).filter(entity ->
!existingProjectIds.contains(entity.getProjectId()))
.collect(Collectors.toList());
invalidEntities.forEach(projectFacility -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.egov.project.repository.ProjectRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
Expand All @@ -31,10 +32,12 @@
public class PsProjectIdValidator implements Validator<ProjectStaffBulkRequest, ProjectStaff> {

private final ProjectRepository projectRepository;
private final RedisTemplate<String,String> redisTemplate;

@Autowired
public PsProjectIdValidator(ProjectRepository projectRepository) {
public PsProjectIdValidator(ProjectRepository projectRepository,RedisTemplate redisTemplate) {
this.projectRepository = projectRepository;
this.redisTemplate = redisTemplate;
}


Expand All @@ -49,8 +52,28 @@ public Map<ProjectStaff, List<Error>> validate(ProjectStaffBulkRequest request)
.stream().filter(notHavingErrors()).collect(Collectors.toList()), idMethod);
if (!eMap.isEmpty()) {
List<String> entityIds = new ArrayList<>(eMap.keySet());
List<String> existingProjectIds = projectRepository.validateIds(entityIds,
List<String> existingProjectIds = new ArrayList<>();
List<String> cacheMissIds = new ArrayList<>();

for (String id : entityIds) {
String redisKey = "project-create-cache-" + id;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Extract Redis key pattern as a constant for better maintainability.

The Redis key pattern is hardcoded. Consider extracting it as a constant to maintain consistency across the codebase.

Add a constant at the class level:

 public class PsProjectIdValidator implements Validator<ProjectStaffBulkRequest, ProjectStaff> {
 
+    private static final String PROJECT_CACHE_KEY_PREFIX = "project-create-cache-";
     private final ProjectRepository projectRepository;
     private final RedisTemplate<String,String> redisTemplate;

Then update line 59:

-                String redisKey = "project-create-cache-" + id;
+                String redisKey = PROJECT_CACHE_KEY_PREFIX + id;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String redisKey = "project-create-cache-" + id;
public class PsProjectIdValidator implements Validator<ProjectStaffBulkRequest, ProjectStaff> {
private static final String PROJECT_CACHE_KEY_PREFIX = "project-create-cache-";
private final ProjectRepository projectRepository;
private final RedisTemplate<String, String> redisTemplate;
// existing constructors and methods...
@Override
public void validate(ProjectStaffBulkRequest request, Errors errors) {
String id = request.getStaff().get(0).getId();
- String redisKey = "project-create-cache-" + id;
+ String redisKey = PROJECT_CACHE_KEY_PREFIX + id;
// existing caching logic...
}
}
🤖 Prompt for AI Agents
In
health-services/project/src/main/java/org/egov/project/validator/staff/PsProjectIdValidator.java
at line 59, the Redis key pattern is hardcoded as a string literal. Extract this
pattern into a private static final constant at the class level to improve
maintainability and consistency. Replace the hardcoded string on line 59 with
this constant concatenated with the variable id.

try {
if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) {
existingProjectIds.add(id); // found in cache
} else {
cacheMissIds.add(id); // not in cache
}
}catch (Exception ex) {
log.error("Redis error while checking key: {}", redisKey, ex);
cacheMissIds.add(id); // fallback to DB if Redis fails
}
}
// Fallback to DB only for cache misses
if (!cacheMissIds.isEmpty()) {
List<String> dbValidIds = projectRepository.validateIds(cacheMissIds,
getIdFieldName(idMethod));
existingProjectIds.addAll(dbValidIds);
}
List<ProjectStaff> invalidEntities = entities.stream().filter(notHavingErrors()).filter(entity ->
!existingProjectIds.contains(entity.getProjectId()))
.collect(Collectors.toList());
Expand Down
Loading