diff --git a/pom.xml b/pom.xml index 84eb1596..563ff4d8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.common.identity identity-api - 3.6.0 + 3.6.1 war @@ -73,6 +73,16 @@ 1.3.2 + org.springframework.boot + spring-boot-starter-data-elasticsearch + + + co.elastic.clients + elasticsearch-java + 8.11.0 + + + org.springframework.boot spring-boot-devtools runtime @@ -281,31 +291,29 @@ 3.3.0 - pl.project13.maven - git-commit-id-plugin - 4.9.10 + io.github.git-commit-id + git-commit-id-maven-plugin + 9.0.2 get-the-git-infos revision + initialize - ${project.basedir}/.git - git - false true - - ${project.build.outputDirectory}/git.properties - - json - - false - false - -dirty - + ${project.build.outputDirectory}/git.properties + + ^git.branch$ + ^git.commit.id.abbrev$ + ^git.build.version$ + ^git.build.time$ + + false + false diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index 75ad8759..98736180 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -23,3 +23,13 @@ fhir-url=@env.FHIR_API@ spring.redis.host=@env.REDIS_HOST@ cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ + +# Elasticsearch Configuration +elasticsearch.host=@env.ELASTICSEARCH_HOST@ +elasticsearch.port=@env.ELASTICSEARCH_PORT@ +elasticsearch.username=@env.ELASTICSEARCH_USERNAME@ +elasticsearch.password=@env.ELASTICSEARCH_PASSWORD@ +elasticsearch.index.beneficiary=@env.ELASTICSEARCH_INDEX_BENEFICIARY@ + +# Enable/Disable ES (for gradual rollout) +elasticsearch.enabled=@env.ELASTICSEARCH_ENABLED@ diff --git a/src/main/environment/common_docker.properties b/src/main/environment/common_docker.properties index 90942c28..8f66c5b0 100644 --- a/src/main/environment/common_docker.properties +++ b/src/main/environment/common_docker.properties @@ -23,3 +23,13 @@ fhir-url=${FHIR_API} spring.redis.host=${REDIS_HOST} cors.allowed-origins=${CORS_ALLOWED_ORIGINS} + +# Elasticsearch Configuration +elasticsearch.host=${ELASTICSEARCH_HOST} +elasticsearch.port=${ELASTICSEARCH_PORT} +elasticsearch.username=${ELASTICSEARCH_USERNAME} +elasticsearch.password=${ELASTICSEARCH_PASSWORD} +elasticsearch.index.beneficiary=${ELASTICSEARCH_INDEX_BENEFICIARY} + +# Enable/Disable ES (for gradual rollout) +elasticsearch.enabled=${ELASTICSEARCH_ENABLED} \ No newline at end of file diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index ad15db00..45018aa6 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -18,3 +18,14 @@ fhir-url=http://localhost:8093/ # Redis Config spring.redis.host=localhost cors.allowed-origins=http://localhost:* + +# Elasticsearch Configuration +elasticsearch.host=localhost +elasticsearch.port=9200 +elasticsearch.username=elastic +elasticsearch.password=piramalES +elasticsearch.index.beneficiary=beneficiary_index + +# Enable/Disable ES (for gradual rollout) +elasticsearch.enabled=true + diff --git a/src/main/java/com/iemr/common/identity/IdentityApplication.java b/src/main/java/com/iemr/common/identity/IdentityApplication.java index 2b6ac32c..462c1866 100644 --- a/src/main/java/com/iemr/common/identity/IdentityApplication.java +++ b/src/main/java/com/iemr/common/identity/IdentityApplication.java @@ -26,10 +26,12 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import com.iemr.common.identity.utils.IEMRApplBeans; @SpringBootApplication +@ComponentScan(basePackages = {"com.iemr.common.identity"}) public class IdentityApplication extends SpringBootServletInitializer { public static void main(String[] args) { diff --git a/src/main/java/com/iemr/common/identity/config/ElasticsearchConfig.java b/src/main/java/com/iemr/common/identity/config/ElasticsearchConfig.java new file mode 100644 index 00000000..5eec312d --- /dev/null +++ b/src/main/java/com/iemr/common/identity/config/ElasticsearchConfig.java @@ -0,0 +1,85 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.common.identity.config; + +import java.io.IOException; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.elasticsearch.client.RestClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ElasticsearchConfig { + + private static final Logger logger = LoggerFactory.getLogger(ElasticsearchConfig.class); + + @Value("${elasticsearch.host}") + private String esHost; + + @Value("${elasticsearch.port}") + private int esPort; + + @Value("${elasticsearch.username}") + private String esUsername; + + @Value("${elasticsearch.password}") + private String esPassword; + + @Value("${elasticsearch.index.beneficiary}") + private String indexName; + + @Bean + public ElasticsearchClient elasticsearchClient() { + BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + AuthScope.ANY, + new UsernamePasswordCredentials(esUsername, esPassword) + ); + + RestClient restClient = RestClient.builder( + new HttpHost(esHost, esPort, "http") + ).setHttpClientConfigCallback(httpClientBuilder -> + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider) + ).build(); + + ElasticsearchTransport transport = new RestClientTransport( + restClient, + new JacksonJsonpMapper() + ); + + return new ElasticsearchClient(transport); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/config/ElasticsearchSyncConfig.java b/src/main/java/com/iemr/common/identity/config/ElasticsearchSyncConfig.java new file mode 100644 index 00000000..74ef9d2b --- /dev/null +++ b/src/main/java/com/iemr/common/identity/config/ElasticsearchSyncConfig.java @@ -0,0 +1,79 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.common.identity.config; + +import java.util.concurrent.Executor; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +/** + * Configuration for async processing and scheduling + */ +@Configuration +@EnableAsync +@EnableScheduling +public class ElasticsearchSyncConfig { + + /** + * Thread pool for Elasticsearch sync operations + * Configured for long-running background jobs + */ + @Bean(name = "elasticsearchSyncExecutor") + public Executor elasticsearchSyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + + // Only 1-2 sync jobs should run at a time to avoid overwhelming DB/ES + executor.setCorePoolSize(2); + executor.setMaxPoolSize(4); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("es-sync-"); + executor.setKeepAliveSeconds(60); + executor.setWaitForTasksToCompleteOnShutdown(true); + executor.setAwaitTerminationSeconds(60); + + // Handle rejected tasks + executor.setRejectedExecutionHandler((r, executor1) -> { + throw new RuntimeException("Elasticsearch sync queue is full. Please wait for current job to complete."); + }); + + executor.initialize(); + return executor; + } + + /** + * General purpose async executor + */ + @Bean(name = "taskExecutor") + public Executor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("async-"); + executor.initialize(); + return executor; + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/controller/IdentityESController.java b/src/main/java/com/iemr/common/identity/controller/IdentityESController.java new file mode 100644 index 00000000..1d671b5b --- /dev/null +++ b/src/main/java/com/iemr/common/identity/controller/IdentityESController.java @@ -0,0 +1,221 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.controller; + +import java.text.SimpleDateFormat; +import java.util.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.iemr.common.identity.service.IdentityService; +import com.iemr.common.identity.service.elasticsearch.ElasticsearchService; +import com.iemr.common.identity.utils.CookieUtil; +import com.iemr.common.identity.utils.JwtUtil; + +import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.HttpServletRequest; + +/** + * Elasticsearch-enabled Beneficiary Search Controller + * All search endpoints with ES support + */ +@RestController +@RequestMapping("/beneficiary") +public class IdentityESController { + + private static final Logger logger = LoggerFactory.getLogger(IdentityESController.class); + + @Autowired + private ElasticsearchService elasticsearchService; + + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private IdentityService idService; + + /** + * MAIN UNIVERSAL SEARCH ENDPOINT + * Searches across all fields - name, phone, ID, etc. + * + * Usage: GET /beneficiary/search?query=vani + * Usage: GET /beneficiary/search?query=9876543210 + */ + @GetMapping("/search") + public ResponseEntity> search(@RequestParam String query, HttpServletRequest request) { + try { + String jwtToken = CookieUtil.getJwtTokenFromCookie(request); + String userId = jwtUtil.getUserIdFromToken(jwtToken); + int userID = Integer.parseInt(userId); + List> results = elasticsearchService.universalSearch(query, userID); + + Map response = new HashMap<>(); + response.put("data", results); + response.put("statusCode", 200); + response.put("errorMessage", "Success"); + response.put("status", "Success"); + + return ResponseEntity.ok(response); + + } catch (Exception e) { + Map errorResponse = new HashMap<>(); + errorResponse.put("data", new ArrayList<>()); + errorResponse.put("statusCode", 500); + errorResponse.put("errorMessage", e.getMessage()); + errorResponse.put("status", "Error"); + + return ResponseEntity.status(500).body(errorResponse); + } + } + + /** + * Elasticsearch-based advance search + */ + + @Operation(summary = "Get beneficiaries by advance search using Elasticsearch") + @PostMapping(path = "/advancedSearchES", headers = "Authorization") + public ResponseEntity> advanceSearchBeneficiariesES( + @RequestBody String searchFilter, + HttpServletRequest request) { + + logger.info("IdentityESController.advanceSearchBeneficiariesES - start {}", searchFilter); + Map response = new HashMap<>(); + + try { + JsonObject searchParams = JsonParser.parseString(searchFilter).getAsJsonObject(); + logger.info("Search params = {}", searchParams); + + String firstName = getString(searchParams, "firstName"); + String middleName = getString(searchParams, "middleName"); + String maritalStatus = getString(searchParams, "maritalStatus"); + String lastName = getString(searchParams, "lastName"); + Integer genderId = getInteger(searchParams, "genderId"); + Date dob = getDate(searchParams, "dob"); + String fatherName = getString(searchParams, "fatherName"); + String spouseName = getString(searchParams, "spouseName"); + String phoneNumber = getString(searchParams, "phoneNumber"); + String beneficiaryId = getString(searchParams, "beneficiaryId"); + String healthId = getString(searchParams, "healthId"); + String aadharNo = getString(searchParams, "aadharNo"); + Boolean is1097 = getBoolean(searchParams, "is1097"); + + Integer stateId = getLocationInt(searchParams, "stateId"); + Integer districtId = getLocationInt(searchParams, "districtId"); + Integer blockId = getLocationInt(searchParams, "blockId"); + Integer villageId = getLocationInt(searchParams, "villageId"); + + String jwtToken = CookieUtil.getJwtTokenFromCookie(request); + Integer userID = Integer.parseInt(jwtUtil.getUserIdFromToken(jwtToken)); + + logger.info( + "ES Advance search - firstName={}, genderId={}, stateId={}, districtId={}, userId={}", + firstName, genderId, stateId, districtId, userID); + + Map searchResults = idService.advancedSearchBeneficiariesES( + firstName, middleName, lastName, genderId, dob, + stateId, districtId, blockId, villageId, + fatherName, spouseName, maritalStatus, phoneNumber, + beneficiaryId, healthId, aadharNo, + userID, null, is1097); + + response.put("data", searchResults.get("data")); + response.put("count", searchResults.get("count")); + response.put("source", searchResults.get("source")); + response.put("statusCode", 200); + response.put("errorMessage", "Success"); + response.put("status", "Success"); + + return ResponseEntity.ok(response); + + } catch (Exception e) { + logger.error("Error in beneficiary ES advance search", e); + response.put("data", Collections.emptyList()); + response.put("count", 0); + response.put("source", "error"); + response.put("statusCode", 500); + response.put("errorMessage", e.getMessage()); + response.put("status", "Error"); + return ResponseEntity.status(500).body(response); + } + } + + // Helper methods to extract values from JsonObject + + private String getString(JsonObject json, String key) { + return json.has(key) && !json.get(key).isJsonNull() + ? json.get(key).getAsString() + : null; + } + + private Integer getInteger(JsonObject json, String key) { + return json.has(key) && !json.get(key).isJsonNull() + ? json.get(key).getAsInt() + : null; + } + + private Boolean getBoolean(JsonObject json, String key) { + return json.has(key) && !json.get(key).isJsonNull() + ? json.get(key).getAsBoolean() + : null; + } + + private Date getDate(JsonObject json, String key) { + if (json.has(key) && !json.get(key).isJsonNull()) { + try { + return new SimpleDateFormat("yyyy-MM-dd") + .parse(json.get(key).getAsString()); + } catch (Exception e) { + logger.error("Invalid date format for {} ", key, e); + } + } + return null; + } + + private Integer getLocationInt(JsonObject root, String field) { + + if (root.has(field) && !root.get(field).isJsonNull()) { + return root.get(field).getAsInt(); + } + + String[] addressKeys = { + "currentAddress", + "permanentAddress", + "emergencyAddress" + }; + + for (String addressKey : addressKeys) { + if (root.has(addressKey) && root.get(addressKey).isJsonObject()) { + JsonObject addr = root.getAsJsonObject(addressKey); + if (addr.has(field) && !addr.get(field).isJsonNull()) { + return addr.get(field).getAsInt(); + } + } + } + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/controller/elasticsearch/ElasticsearchSyncController.java b/src/main/java/com/iemr/common/identity/controller/elasticsearch/ElasticsearchSyncController.java new file mode 100644 index 00000000..7d8f1877 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/controller/elasticsearch/ElasticsearchSyncController.java @@ -0,0 +1,484 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.controller.elasticsearch; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import com.iemr.common.identity.data.elasticsearch.ElasticsearchSyncJob; +import com.iemr.common.identity.repo.BenMappingRepo; +import com.iemr.common.identity.service.elasticsearch.ElasticsearchSyncService; +import com.iemr.common.identity.service.elasticsearch.SyncJobService; +import com.iemr.common.identity.service.elasticsearch.ElasticsearchSyncService.SyncStatus; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.springframework.http.ResponseEntity; +import com.iemr.common.identity.utils.response.OutputResponse; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; + +import com.iemr.common.identity.domain.MBeneficiarymapping; +import com.iemr.common.identity.service.elasticsearch.ElasticsearchIndexingService; + +/** + * Controller to manage Elasticsearch synchronization operations + * Supports both synchronous and asynchronous sync jobs + */ +@RestController +@RequestMapping("/elasticsearch") +public class ElasticsearchSyncController { + + private static final Logger logger = LoggerFactory.getLogger(ElasticsearchSyncController.class); + + @Autowired + private ElasticsearchSyncService syncService; + + @Autowired + private SyncJobService syncJobService; + + @Autowired + private BenMappingRepo mappingRepo; + + @Autowired + private ElasticsearchIndexingService indexingService; + + @Autowired + private ElasticsearchClient esClient; + + @Value("${elasticsearch.index.beneficiary}") + private String beneficiaryIndex; + + /** + * Start async full sync (RECOMMENDED for millions of records) + * Returns immediately with job ID for tracking + * + * Usage: POST http://localhost:8094/elasticsearch/start + */ + @PostMapping("/start") + public ResponseEntity> startAsyncFullSync( + @RequestParam(required = false, defaultValue = "API") String triggeredBy) { + + logger.info("Received request to start ASYNC full sync"); + + Map response = new HashMap<>(); + + try { + ElasticsearchSyncJob job = syncJobService.startFullSyncJob(triggeredBy); + + response.put("status", "success"); + response.put("message", "Sync job started in background"); + response.put("jobId", job.getJobId()); + response.put("jobStatus", job.getStatus()); + response.put("checkStatusUrl", "/elasticsearch/status/" + job.getJobId()); + + return ResponseEntity.ok(response); + + } catch (RuntimeException e) { + logger.error("Error starting async sync: {}", e.getMessage()); + response.put("status", "error"); + response.put("message", e.getMessage()); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + + } catch (Exception e) { + logger.error("Unexpected error: {}", e.getMessage(), e); + response.put("status", "error"); + response.put("message", "Unexpected error: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); + } + } + + /** + * Get job status by ID + * + * Usage: GET http://localhost:8094/elasticsearch/status/{jobid} + */ + @GetMapping("/status/{jobId}") + public ResponseEntity> getAsyncJobStatus(@PathVariable Long jobId) { + logger.info("Checking status for job: {}", jobId); + + try { + ElasticsearchSyncJob job = syncJobService.getJobStatus(jobId); + + Map response = new HashMap<>(); + response.put("jobId", job.getJobId()); + response.put("jobType", job.getJobType()); + response.put("status", job.getStatus()); + response.put("totalRecords", job.getTotalRecords()); + response.put("processedRecords", job.getProcessedRecords()); + response.put("successCount", job.getSuccessCount()); + response.put("failureCount", job.getFailureCount()); + response.put("progressPercentage", String.format("%.2f", job.getProgressPercentage())); + response.put("processingSpeed", job.getProcessingSpeed()); + response.put("estimatedTimeRemaining", job.getEstimatedTimeRemaining()); + response.put("startedAt", job.getStartedAt()); + response.put("completedAt", job.getCompletedAt()); + response.put("errorMessage", job.getErrorMessage()); + + return ResponseEntity.ok(response); + + } catch (RuntimeException e) { + Map response = new HashMap<>(); + response.put("status", "error"); + response.put("message", e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); + } + } + + /** + * Get all active jobs + * + * Usage: GET http://localhost:8094/elasticsearch/active + */ + @GetMapping("/active") + public ResponseEntity> getActiveJobs() { + logger.info("Fetching active jobs"); + return ResponseEntity.ok(syncJobService.getActiveJobs()); + } + + /** + * Get recent jobs + * + * Usage: GET http://localhost:8080/elasticsearch/recent + */ + @GetMapping("/recent") + public ResponseEntity> getRecentJobs() { + logger.info("Fetching recent jobs"); + return ResponseEntity.ok(syncJobService.getRecentJobs()); + } + + /** + * Resume a failed job + * + * Usage: POST http://localhost:8094/elasticsearch/resume/{jobid} + */ + @PostMapping("/resume/{jobId}") + public ResponseEntity> resumeJob( + @PathVariable Long jobId, + @RequestParam(required = false, defaultValue = "API") String triggeredBy) { + + logger.info("Resuming job: {}", jobId); + + Map response = new HashMap<>(); + + try { + ElasticsearchSyncJob job = syncJobService.resumeJob(jobId, triggeredBy); + + response.put("status", "success"); + response.put("message", "Job resumed"); + response.put("jobId", job.getJobId()); + response.put("resumedFromOffset", job.getCurrentOffset()); + + return ResponseEntity.ok(response); + + } catch (RuntimeException e) { + response.put("status", "error"); + response.put("message", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + } + + /** + * Cancel a running job + * + * Usage: POST http://localhost:8094/elasticsearch/cancel/{jobid} + */ + @PostMapping("/cancel/{jobId}") + public ResponseEntity> cancelJob(@PathVariable Long jobId) { + logger.info("Cancelling job: {}", jobId); + + Map response = new HashMap<>(); + boolean cancelled = syncJobService.cancelJob(jobId); + + if (cancelled) { + response.put("status", "success"); + response.put("message", "Job cancelled"); + return ResponseEntity.ok(response); + } else { + response.put("status", "error"); + response.put("message", "Could not cancel job. It may not be active."); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + } + + /** + * LEGACY: Synchronous full sync(NOT recommended for large datasets) + * Use /start instead + * + * Usage: POST http://localhost:8094/elasticsearch/all + */ + @PostMapping("/all") + public ResponseEntity> syncAllBeneficiaries() { + logger.warn("LEGACY sync endpoint called. Consider using /start instead."); + logger.info("Received request to sync all beneficiaries (BLOCKING)"); + + Map response = new HashMap<>(); + + try { + ElasticsearchSyncService.SyncResult result = syncService.syncAllBeneficiaries(); + + response.put("status", "completed"); + response.put("successCount", result.getSuccessCount()); + response.put("failureCount", result.getFailureCount()); + response.put("error", result.getError()); + response.put("warning", "This is a blocking operation. For large datasets, use /start"); + + if (result.getError() != null) { + return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).body(response); + } + + return ResponseEntity.ok(response); + + } catch (Exception e) { + logger.error("Error in sync all endpoint: {}", e.getMessage(), e); + response.put("status", "error"); + response.put("message", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); + } + } + + /** + * Sync a single beneficiary by BenRegId + * + * Usage: POST http://localhost:8094/elasticsearch/single/123456 + */ + @PostMapping("/single/{benRegId}") + public ResponseEntity> syncSingleBeneficiary( + @PathVariable String benRegId) { + + Map response = new HashMap<>(); + + try { + boolean success = syncService.syncSingleBeneficiary(benRegId); + + response.put("status", success ? "success" : "failed"); + response.put("benRegId", benRegId); + response.put("synced", success); + + if (!success) { + response.put("message", "Beneficiary not found in database or sync failed. Check logs for details."); + } else { + response.put("message", "Beneficiary successfully synced to Elasticsearch"); + } + + return ResponseEntity.ok(response); + + } catch (Exception e) { + logger.error("Error syncing single beneficiary: {}", e.getMessage(), e); + response.put("status", "error"); + response.put("benRegId", benRegId); + response.put("synced", false); + response.put("message", "Exception occurred: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); + } + } + + /** + * Check sync status - compare DB count vs ES count + * + * Usage: GET http://localhost:8094/elasticsearch/status + */ + @GetMapping("/status") + public ResponseEntity checkSyncStatus() { + logger.info("Received request to check sync status"); + + try { + SyncStatus status = syncService.checkSyncStatus(); + return ResponseEntity.ok(status); + + } catch (Exception e) { + logger.error("Error checking sync status: {}", e.getMessage(), e); + SyncStatus errorStatus = new SyncStatus(); + errorStatus.setError(e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorStatus); + } + } + + /** + * Health check endpoint + * + * Usage: GET http://localhost:8094/elasticsearch/health + */ + @GetMapping("/health") + public ResponseEntity> healthCheck() { + Map response = new HashMap<>(); + response.put("status", "UP"); + response.put("service", "Elasticsearch Sync Service"); + response.put("asyncJobsRunning", syncJobService.isFullSyncRunning()); + response.put("activeJobs", syncJobService.getActiveJobs().size()); + return ResponseEntity.ok(response); + } + + /** + * Debug endpoint to check if a beneficiary exists in database + * + * Usage: GET http://localhost:8094/elasticsearch/debug/check/123456 + */ + @GetMapping("/debug/check/{benRegId}") + public ResponseEntity> checkBeneficiaryExists( + @PathVariable String benRegId) { + + Map response = new HashMap<>(); + + try { + java.math.BigInteger benRegIdBig = new java.math.BigInteger(benRegId); + + boolean exists = mappingRepo.existsByBenRegId(benRegIdBig); + + response.put("benRegId", benRegId); + response.put("existsInDatabase", exists); + + if (exists) { + response.put("message", "Beneficiary found in database"); + + MBeneficiarymapping mapping = + mappingRepo.findByBenRegId(benRegIdBig); + + if (mapping != null) { + response.put("benMapId", mapping.getBenMapId()); + response.put("deleted", mapping.getDeleted()); + response.put("hasDetails", mapping.getMBeneficiarydetail() != null); + response.put("hasContact", mapping.getMBeneficiarycontact() != null); + } + } else { + response.put("message", "Beneficiary NOT found in database"); + } + + return ResponseEntity.ok(response); + + } catch (Exception e) { + logger.error("Error checking beneficiary: {}", e.getMessage(), e); + response.put("status", "error"); + response.put("message", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); + } + } + + /** + * Create or recreate the Elasticsearch index with proper mapping + * This will DELETE the existing index and create a new one + * + * POST /elasticsearch/index/create + */ + @PostMapping("/index/create") + public ResponseEntity createIndex() { + logger.info("API: Create Elasticsearch index request received"); + OutputResponse response = new OutputResponse(); + + try { + indexingService.createIndexWithMapping(); + + response.setResponse("Index created successfully. Ready for data sync."); + logger.info("Index created successfully"); + + return ResponseEntity.ok(response); + + } catch (Exception e) { + logger.error("Error creating index: {}", e.getMessage(), e); + response.setError(5000, "Error creating index: " + e.getMessage()); + return ResponseEntity.status(500).body(response); + } + } + + /** + * Recreate index and immediately start syncing data + * This is a convenience endpoint that does both operations + * + * POST /elasticsearch/index/recreate-and-sync + */ + @PostMapping("/index/recreate-and-sync") + public ResponseEntity recreateAndSync() { + logger.info("API: Recreate index and sync request received"); + OutputResponse response = new OutputResponse(); + + try { + // Step 1: Recreate index + logger.info("Step 1: Recreating index..."); + indexingService.createIndexWithMapping(); + logger.info("Index recreated successfully"); + + // Step 2: Start sync + logger.info("Step 2: Starting data sync..."); + Map syncResult = indexingService.indexAllBeneficiaries(); + + response.setResponse("Index recreated and sync started. Success: " + + syncResult.get("success") + ", Failed: " + syncResult.get("failed")); + + return ResponseEntity.ok(response); + + } catch (Exception e) { + logger.error("Error in recreate and sync: {}", e.getMessage(), e); + response.setError(5000, "Error: " + e.getMessage()); + return ResponseEntity.status(500).body(response); + } + } + + /** + * Get information about the current index + * Shows mapping, document count, etc. + * + * GET /elasticsearch/index/info + */ + @GetMapping("/index/info") + public ResponseEntity getIndexInfo() { + logger.info("API: Get index info request received"); + OutputResponse response = new OutputResponse(); + + try { + // You can add code here to get index stats using esClient + response.setResponse("Index info endpoint - implementation pending"); + return ResponseEntity.ok(response); + + } catch (Exception e) { + logger.error("Error getting index info: {}", e.getMessage(), e); + response.setError(5000, "Error: " + e.getMessage()); + return ResponseEntity.status(500).body(response); + } + } + + @PostMapping("/refresh") +public ResponseEntity> refreshIndex() { + Map response = new HashMap<>(); + + try { + logger.info("Manual refresh requested"); + + esClient.indices().refresh(r -> r.index(beneficiaryIndex)); + + response.put("status", "success"); + response.put("message", "Index refreshed - all data is now searchable"); + + return ResponseEntity.ok(response); + + } catch (Exception e) { + logger.error("Refresh failed: {}", e.getMessage(), e); + response.put("status", "error"); + response.put("message", "Refresh failed: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); + } +} +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/controller/health/HealthController.java b/src/main/java/com/iemr/common/identity/controller/health/HealthController.java new file mode 100644 index 00000000..b7cc3641 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/controller/health/HealthController.java @@ -0,0 +1,90 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.controller.health; + +import java.time.Instant; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import com.iemr.common.identity.service.health.HealthService; +import com.iemr.common.identity.utils.JwtAuthenticationUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + + +@RestController +@RequestMapping("/health") +@Tag(name = "Health Check", description = "APIs for checking infrastructure health status") +public class HealthController { + + private static final Logger logger = LoggerFactory.getLogger(HealthController.class); + + private final HealthService healthService; + private final JwtAuthenticationUtil jwtAuthenticationUtil; + + public HealthController(HealthService healthService, JwtAuthenticationUtil jwtAuthenticationUtil) { + this.healthService = healthService; + this.jwtAuthenticationUtil = jwtAuthenticationUtil; + } + @GetMapping + @Operation(summary = "Check infrastructure health", + description = "Returns the health status of MySQL, Redis, Elasticsearch, and other configured services") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Services are UP or DEGRADED (operational with warnings)"), + @ApiResponse(responseCode = "503", description = "One or more critical services are DOWN") + }) + public ResponseEntity> checkHealth() { + logger.info("Health check endpoint called"); + + try { + Map healthStatus = healthService.checkHealth(); + String overallStatus = (String) healthStatus.get("status"); + + // Return 503 only if DOWN; 200 for both UP and DEGRADED (DEGRADED = operational with warnings) + HttpStatus httpStatus = "DOWN".equals(overallStatus) ? HttpStatus.SERVICE_UNAVAILABLE : HttpStatus.OK; + + logger.debug("Health check completed with status: {}", overallStatus); + return new ResponseEntity<>(healthStatus, httpStatus); + + } catch (Exception e) { + logger.error("Unexpected error during health check", e); + + // Return sanitized error response + Map errorResponse = Map.of( + "status", "DOWN", + "timestamp", Instant.now().toString() + ); + + return new ResponseEntity<>(errorResponse, HttpStatus.SERVICE_UNAVAILABLE); + } + } +} diff --git a/src/main/java/com/iemr/common/identity/controller/version/VersionController.java b/src/main/java/com/iemr/common/identity/controller/version/VersionController.java index 4435c3db..39eee9f1 100644 --- a/src/main/java/com/iemr/common/identity/controller/version/VersionController.java +++ b/src/main/java/com/iemr/common/identity/controller/version/VersionController.java @@ -21,54 +21,59 @@ */ package com.iemr.common.identity.controller.version; -import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import com.iemr.common.identity.utils.response.OutputResponse; - import io.swagger.v3.oas.annotations.Operation; @RestController public class VersionController { - private Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); + private final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); + private static final String UNKNOWN_VALUE = "unknown"; + @Operation(summary = "Get version information") - @GetMapping(value = "/version",consumes = "application/json", produces = "application/json") - public String versionInformation() { - OutputResponse output = new OutputResponse(); + @GetMapping(value = "/version", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity> versionInformation() { + Map response = new LinkedHashMap<>(); try { logger.info("version Controller Start"); - output.setResponse(readGitProperties()); - } catch (Exception e) { - output.setError(e); - } - + Properties gitProperties = loadGitProperties(); + response.put("buildTimestamp", gitProperties.getProperty("git.build.time", UNKNOWN_VALUE)); + response.put("version", gitProperties.getProperty("git.build.version", UNKNOWN_VALUE)); + response.put("branch", gitProperties.getProperty("git.branch", UNKNOWN_VALUE)); + response.put("commitHash", gitProperties.getProperty("git.commit.id.abbrev", UNKNOWN_VALUE)); + } catch (Exception e) { + logger.error("Failed to load version information", e); + response.put("buildTimestamp", UNKNOWN_VALUE); + response.put("version", UNKNOWN_VALUE); + response.put("branch", UNKNOWN_VALUE); + response.put("commitHash", UNKNOWN_VALUE); + } logger.info("version Controller End"); - return output.toString(); + return ResponseEntity.ok(response); } - private String readGitProperties() throws Exception { - ClassLoader classLoader = getClass().getClassLoader(); - InputStream inputStream = classLoader.getResourceAsStream("git.properties"); - - return readFromInputStream(inputStream); - } - private String readFromInputStream(InputStream inputStream) - throws IOException { - StringBuilder resultStringBuilder = new StringBuilder(); - try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { - String line; - while ((line = br.readLine()) != null) { - resultStringBuilder.append(line).append("\n"); - } - } - return resultStringBuilder.toString(); + + private Properties loadGitProperties() throws IOException { + Properties properties = new Properties(); + try (InputStream input = getClass().getClassLoader() + .getResourceAsStream("git.properties")) { + if (input != null) { + properties.load(input); + } + } + return properties; } } diff --git a/src/main/java/com/iemr/common/identity/data/elasticsearch/BeneficiaryDocument.java b/src/main/java/com/iemr/common/identity/data/elasticsearch/BeneficiaryDocument.java new file mode 100644 index 00000000..acd66c1c --- /dev/null +++ b/src/main/java/com/iemr/common/identity/data/elasticsearch/BeneficiaryDocument.java @@ -0,0 +1,172 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.data.elasticsearch; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import java.util.Date; + +@Data +public class BeneficiaryDocument { + + @JsonProperty("benId") + private String benId; + + @JsonProperty("benRegId") + private Long benRegId; + + @JsonProperty("beneficiaryID") + private String beneficiaryID; + + @JsonProperty("firstName") + private String firstName; + + @JsonProperty("middleName") + private String middleName; + + @JsonProperty("lastName") + private String lastName; + + @JsonProperty("fatherName") + private String fatherName; + + @JsonProperty("spouseName") + private String spouseName; + + @JsonProperty("maritalStatusID") + private Integer maritalStatusID; + + @JsonProperty("maritalStatusName") + private String maritalStatusName; + + @JsonProperty("age") + private Integer age; + + @JsonProperty("dOB") + private Date dOB; + + @JsonProperty("gender") + private String gender; + + @JsonProperty("genderID") + private Integer genderID; + + @JsonProperty("genderName") + private String genderName; + + @JsonProperty("phoneNum") + private String phoneNum; + + @JsonProperty("aadharNo") + private String aadharNo; + + @JsonProperty("govtIdentityNo") + private String govtIdentityNo; + + @JsonProperty("healthID") + private String healthID; + + @JsonProperty("abhaID") + private String abhaID; + + @JsonProperty("abhaCreatedDate") + private String abhaCreatedDate; + + @JsonProperty("familyID") + private String familyID; + + @JsonProperty("stateID") + private Integer stateID; + + @JsonProperty("stateName") + private String stateName; + + @JsonProperty("districtID") + private Integer districtID; + + @JsonProperty("districtName") + private String districtName; + + @JsonProperty("blockID") + private Integer blockID; + + @JsonProperty("blockName") + private String blockName; + + @JsonProperty("villageID") + private Integer villageID; + + @JsonProperty("villageName") + private String villageName; + + @JsonProperty("pinCode") + private String pinCode; + + @JsonProperty("servicePointID") + private Integer servicePointID; + + @JsonProperty("servicePointName") + private String servicePointName; + + @JsonProperty("parkingPlaceID") + private Integer parkingPlaceID; + + @JsonProperty("permStateID") + private Integer permStateID; + + @JsonProperty("permStateName") + private String permStateName; + + @JsonProperty("permDistrictID") + private Integer permDistrictID; + + @JsonProperty("permDistrictName") + private String permDistrictName; + + @JsonProperty("permBlockID") + private Integer permBlockID; + + @JsonProperty("permBlockName") + private String permBlockName; + + @JsonProperty("permVillageID") + private Integer permVillageID; + + @JsonProperty("permVillageName") + private String permVillageName; + + @JsonProperty("createdBy") + private String createdBy; + + @JsonProperty("createdDate") + private Date createdDate; + + @JsonProperty("lastModDate") + private Long lastModDate; + + @JsonProperty("benAccountID") + private Long benAccountID; + + @JsonProperty("isHIVPos") + private String isHIVPos; +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/data/elasticsearch/ElasticsearchSyncJob.java b/src/main/java/com/iemr/common/identity/data/elasticsearch/ElasticsearchSyncJob.java new file mode 100644 index 00000000..c2f5c2f0 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/data/elasticsearch/ElasticsearchSyncJob.java @@ -0,0 +1,116 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.data.elasticsearch; + +import java.sql.Timestamp; +import jakarta.persistence.*; +import lombok.Data; + +/** + * Entity to track Elasticsearch sync job status + */ +@Entity +@Table(name = "t_elasticsearch_sync_job") +@Data +public class ElasticsearchSyncJob { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "job_id") + private Long jobId; + + @Column(name = "job_type", length = 50, nullable = false) + private String jobType; // FULL_SYNC, INCREMENTAL_SYNC, SINGLE_BENEFICIARY + + @Column(name = "status", length = 50, nullable = false) + private String status; // PENDING, RUNNING, COMPLETED, FAILED, CANCELLED + + @Column(name = "total_records") + private Long totalRecords; + + @Column(name = "processed_records") + private Long processedRecords; + + @Column(name = "success_count") + private Long successCount; + + @Column(name = "failure_count") + private Long failureCount; + + @Column(name = "current_offset") + private Integer currentOffset; + + @Column(name = "started_at") + private Timestamp startedAt; + + @Column(name = "completed_at") + private Timestamp completedAt; + + @Column(name = "error_message", columnDefinition = "TEXT") + private String errorMessage; + + @Column(name = "triggered_by", length = 100) + private String triggeredBy; // User who triggered the job + + @Column(name = "created_date", nullable = false, updatable = false) + private Timestamp createdDate; + + @Column(name = "last_updated") + private Timestamp lastUpdated; + + @Column(name = "estimated_time_remaining") + private Long estimatedTimeRemaining; // in seconds + + @Column(name = "processing_speed") + private Double processingSpeed; // records per second + + @PrePersist + protected void onCreate() { + createdDate = new Timestamp(System.currentTimeMillis()); + lastUpdated = createdDate; + } + + @PreUpdate + protected void onUpdate() { + lastUpdated = new Timestamp(System.currentTimeMillis()); + } + + /** + * Calculate progress percentage + */ + @Transient + public double getProgressPercentage() { + if (totalRecords == null || totalRecords == 0) { + return 0.0; + } + return (processedRecords * 100.0) / totalRecords; + } + + /** + * Check if job is active (running) + */ + @Transient + public boolean isActive() { + return "RUNNING".equals(status) || "PENDING".equals(status); + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java index e477ea5d..4cab210c 100644 --- a/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java +++ b/src/main/java/com/iemr/common/identity/data/rmnch/RMNCHBeneficiaryDetailsRmnch.java @@ -499,4 +499,55 @@ public class RMNCHBeneficiaryDetailsRmnch { @Column(name = "noOfDaysForDelivery") private Integer noOfDaysForDelivery; + + @Expose + private Boolean isDeath; + + @Expose + private String isDeathValue; + + @Expose + private String dateOfDeath; + + @Expose + private String timeOfDeath; + + @Expose + private String reasonOfDeath; + + @Expose + private Integer reasonOfDeathId; + + @Expose + private String placeOfDeath; + + @Expose + private Integer placeOfDeathId; + + @Expose + private String otherPlaceOfDeath; + + @Expose + private Boolean isSpouseAdded; + + + @Expose + private Boolean isChildrenAdded; + + @Expose + private Boolean isMarried; + + @Expose + private Boolean doYouHavechildren; + + + @Expose + private Integer noofAlivechildren; + + @Expose + private Integer noOfchildren; + + @Expose + private Boolean isDeactivate; + } diff --git a/src/main/java/com/iemr/common/identity/domain/User.java b/src/main/java/com/iemr/common/identity/domain/User.java index 2a88b2e1..38fca023 100644 --- a/src/main/java/com/iemr/common/identity/domain/User.java +++ b/src/main/java/com/iemr/common/identity/domain/User.java @@ -15,7 +15,7 @@ import jakarta.persistence.Table; import lombok.Data; @Entity -@Table(name = "m_User",schema = "db_iemr") +@Table(schema = "db_iemr", name = "m_user") @Data @JsonIgnoreProperties(ignoreUnknown = true) public class User { diff --git a/src/main/java/com/iemr/common/identity/dto/BeneficiariesESDTO.java b/src/main/java/com/iemr/common/identity/dto/BeneficiariesESDTO.java new file mode 100644 index 00000000..e7de32f8 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/dto/BeneficiariesESDTO.java @@ -0,0 +1,209 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; +import java.util.Date; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class BeneficiariesESDTO { + + @JsonProperty("benRegId") + private Long benRegId; + + @JsonProperty("beneficiaryID") + private String beneficiaryID; + + @JsonProperty("firstName") + private String firstName; + + @JsonProperty("middleName") + private String middleName; + + @JsonProperty("lastName") + private String lastName; + + @JsonProperty("genderID") + private Integer genderID; + + @JsonProperty("genderName") + private String genderName; + + @JsonProperty("dOB") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") + private Date dOB; + + @JsonProperty("age") + private Integer age; + + @JsonProperty("phoneNum") + private String phoneNum; + + // @JsonProperty("aadharNo") + // private String aadharNo; + + // @JsonProperty("govtIdentityNo") + // private String govtIdentityNo; + + @JsonProperty("fatherName") + private String fatherName; + + @JsonProperty("spouseName") + private String spouseName; + + @JsonProperty("maritalStatusID") + private Integer maritalStatusID; + + @JsonProperty("maritalStatusName") + private String maritalStatusName; + + @JsonProperty("createdBy") + private String createdBy; + + @JsonProperty("createdDate") + private Date createdDate; + + @JsonProperty("lastModDate") + private Long lastModDate; + + @JsonProperty("benAccountID") + private Long benAccountID; + + @JsonProperty("isHIVPos") + private String isHIVPos; + + @JsonProperty("healthID") + private String healthID; + + @JsonProperty("abhaID") + private String abhaID; + + @JsonProperty("abhaCreatedDate") + private String abhaCreatedDate; + + @JsonProperty("familyID") + private String familyID; + + @JsonProperty("stateID") + private Integer stateID; + + @JsonProperty("stateName") + private String stateName; + + @JsonProperty("stateCode") + private String stateCode; + + @JsonProperty("districtID") + private Integer districtID; + + @JsonProperty("districtName") + private String districtName; + + @JsonProperty("blockID") + private Integer blockID; + + @JsonProperty("blockName") + private String blockName; + + @JsonProperty("villageID") + private Integer villageID; + + @JsonProperty("villageName") + private String villageName; + + @JsonProperty("districtBranchID") + private Integer districtBranchID; + + @JsonProperty("districtBranchName") + private String districtBranchName; + + @JsonProperty("parkingPlaceID") + private Integer parkingPlaceID; + + @JsonProperty("servicePointID") + private Integer servicePointID; + + @JsonProperty("servicePointName") + private String servicePointName; + + @JsonProperty("pinCode") + private String pinCode; + + @JsonProperty("permStateID") + private Integer permStateID; + + @JsonProperty("permStateName") + private String permStateName; + + @JsonProperty("permDistrictID") + private Integer permDistrictID; + + @JsonProperty("permDistrictName") + private String permDistrictName; + + @JsonProperty("permBlockID") + private Integer permBlockID; + + @JsonProperty("permBlockName") + private String permBlockName; + + @JsonProperty("permVillageID") + private Integer permVillageID; + + @JsonProperty("permVillageName") + private String permVillageName; + + @JsonProperty("phoneNumbers") + private List phoneNumbers; + + @Data + @NoArgsConstructor + @AllArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public static class PhoneNumberDTO { + @JsonProperty("benPhMapID") + private Long benPhMapID; + + @JsonProperty("phoneNo") + private String phoneNo; + + @JsonProperty("parentBenRegID") + private Long parentBenRegID; + + @JsonProperty("benRelationshipID") + private Integer benRelationshipID; + + @JsonProperty("benRelationshipType") + private String benRelationshipType; + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/mapper/BeneficiaryESMapper.java b/src/main/java/com/iemr/common/identity/mapper/BeneficiaryESMapper.java new file mode 100644 index 00000000..412b0f09 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/mapper/BeneficiaryESMapper.java @@ -0,0 +1,158 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.mapper; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +public class BeneficiaryESMapper { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + public Map transformESResponse(List> esResults) { + List> transformedData = new ArrayList<>(); + + for (Map esDoc : esResults) { + Map beneficiary = new HashMap<>(); + + beneficiary.put("beneficiaryRegID", esDoc.get("beneficiaryRegID")); + beneficiary.put("beneficiaryID", esDoc.get("beneficiaryID")); + beneficiary.put("firstName", esDoc.get("firstName")); + beneficiary.put("lastName", esDoc.get("lastName")); + beneficiary.put("genderID", esDoc.get("genderID")); + beneficiary.put("genderName", esDoc.get("genderName")); + beneficiary.put("dOB", esDoc.get("dOB")); + beneficiary.put("dob", esDoc.get("dOB")); + beneficiary.put("age", esDoc.get("age")); + beneficiary.put("actualAge", esDoc.get("age")); + beneficiary.put("ageUnits", "Years"); + beneficiary.put("fatherName", esDoc.getOrDefault("fatherName", "")); + beneficiary.put("spouseName", esDoc.getOrDefault("spouseName", "")); + beneficiary.put("isHIVPos", esDoc.getOrDefault("isHIVPos", "")); + beneficiary.put("createdBy", esDoc.get("createdBy")); + beneficiary.put("createdDate", esDoc.get("createdDate")); + beneficiary.put("lastModDate", esDoc.get("lastModDate")); + beneficiary.put("benAccountID", esDoc.get("benAccountID")); + + Map mGender = new HashMap<>(); + mGender.put("genderID", esDoc.get("genderID")); + mGender.put("genderName", esDoc.get("genderName")); + beneficiary.put("m_gender", mGender); + + Map demographics = (Map) esDoc.get("demographics"); + if (demographics != null) { + Map benDemographics = new HashMap<>(demographics); + benDemographics.put("beneficiaryRegID", esDoc.get("beneficiaryRegID")); + + benDemographics.put("m_state", createStateObject(demographics)); + benDemographics.put("m_district", createDistrictObject(demographics)); + benDemographics.put("m_districtblock", createBlockObject(demographics)); + benDemographics.put("m_districtbranchmapping", createBranchObject(demographics)); + + beneficiary.put("i_bendemographics", benDemographics); + } + + List> phoneMaps = new ArrayList<>(); + List> phoneNumbers = (List>) esDoc.get("phoneNumbers"); + if (phoneNumbers != null && !phoneNumbers.isEmpty()) { + for (Map phone : phoneNumbers) { + Map phoneMap = new HashMap<>(phone); + phoneMap.put("benificiaryRegID", esDoc.get("beneficiaryRegID")); + + Map relationType = new HashMap<>(); + relationType.put("benRelationshipID", phone.get("benRelationshipID")); + relationType.put("benRelationshipType", phone.get("benRelationshipType")); + phoneMap.put("benRelationshipType", relationType); + + phoneMaps.add(phoneMap); + } + } + beneficiary.put("benPhoneMaps", phoneMaps); + + beneficiary.put("isConsent", false); + beneficiary.put("m_title", new HashMap<>()); + beneficiary.put("maritalStatus", new HashMap<>()); + beneficiary.put("changeInSelfDetails", false); + beneficiary.put("changeInAddress", false); + beneficiary.put("changeInContacts", false); + beneficiary.put("changeInIdentities", false); + beneficiary.put("changeInOtherDetails", false); + beneficiary.put("changeInFamilyDetails", false); + beneficiary.put("changeInAssociations", false); + beneficiary.put("changeInBankDetails", false); + beneficiary.put("changeInBenImage", false); + beneficiary.put("is1097", false); + beneficiary.put("emergencyRegistration", false); + beneficiary.put("passToNurse", false); + beneficiary.put("beneficiaryIdentities", new ArrayList<>()); + + transformedData.add(beneficiary); + } + + Map response = new HashMap<>(); + response.put("data", transformedData); + response.put("statusCode", 200); + response.put("errorMessage", "Success"); + response.put("status", "Success"); + + return response; + } + + private Map createStateObject(Map demographics) { + Map state = new HashMap<>(); + state.put("stateID", demographics.get("stateID")); + state.put("stateName", demographics.get("stateName")); + state.put("stateCode", demographics.get("stateCode")); + state.put("countryID", 1); + return state; + } + + private Map createDistrictObject(Map demographics) { + Map district = new HashMap<>(); + district.put("districtID", demographics.get("districtID")); + district.put("districtName", demographics.get("districtName")); + district.put("stateID", demographics.get("stateID")); + return district; + } + + private Map createBlockObject(Map demographics) { + Map block = new HashMap<>(); + block.put("blockID", demographics.get("blockID")); + block.put("blockName", demographics.get("blockName")); + block.put("districtID", demographics.get("districtID")); + block.put("stateID", demographics.get("stateID")); + return block; + } + + private Map createBranchObject(Map demographics) { + Map branch = new HashMap<>(); + branch.put("districtBranchID", demographics.get("districtBranchID")); + branch.put("blockID", demographics.get("blockID")); + branch.put("villageName", demographics.get("districtBranchName")); + branch.put("pinCode", demographics.get("pinCode")); + return branch; + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/repo/BenAddressRepo.java b/src/main/java/com/iemr/common/identity/repo/BenAddressRepo.java index d9339ab2..5d79f8ae 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenAddressRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenAddressRepo.java @@ -18,7 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see https://www.gnu.org/licenses/. -*/ + */ package com.iemr.common.identity.repo; import java.math.BigInteger; @@ -36,42 +36,59 @@ @Repository public interface BenAddressRepo extends CrudRepository { - List findByBenAddressIDOrderByBenAddressIDAsc(BigInteger benAddressID); - List findByCurrPinCodeOrderByBenAddressIDAsc(String currPinCode); + List findByBenAddressIDOrderByBenAddressIDAsc(BigInteger benAddressID); - List findByPermPinCodeOrderByBenAddressIDAsc(String permPinCode); + List findByCurrPinCodeOrderByBenAddressIDAsc(String currPinCode); - List findByCurrStateAndCurrDistrictOrderByBenAddressIDAsc(String currState, String currDist); + List findByPermPinCodeOrderByBenAddressIDAsc(String permPinCode); - List findByCurrStateIdAndCurrDistrictIdOrderByBenAddressIDAsc(Integer currStateID, - Integer currDistID); + List findByCurrStateAndCurrDistrictOrderByBenAddressIDAsc(String currState, String currDist); - List findByPermStateAndPermDistrictOrderByBenAddressIDAsc(String permState, String permDist); + List findByCurrStateIdAndCurrDistrictIdOrderByBenAddressIDAsc(Integer currStateID, + Integer currDistID); - List findByPermStateIdAndPermDistrictIdOrderByBenAddressIDAsc(Integer permStateID, - Integer permDistID); + List findByPermStateAndPermDistrictOrderByBenAddressIDAsc(String permState, String permDist); - List findByEmerStateAndEmerDistrictOrderByBenAddressIDAsc(String emerState, String emerDist); + List findByPermStateIdAndPermDistrictIdOrderByBenAddressIDAsc(Integer permStateID, + Integer permDistID); - List findByEmerStateIdAndEmerDistrictIdOrderByBenAddressIDAsc(Integer emerStateID, - Integer emerDistID); + List findByEmerStateAndEmerDistrictOrderByBenAddressIDAsc(String emerState, String emerDist); - List findByCreatedDateBetweenOrderByBenAddressIDAsc(Timestamp fromDate, Timestamp toDate); + List findByEmerStateIdAndEmerDistrictIdOrderByBenAddressIDAsc(Integer emerStateID, + Integer emerDistID); - @Query("select a from MBeneficiaryaddress a where a.currAddressValue = :address or " - + "a.emerAddressValue = :address or a.permAddressValue = :address order by a.benAddressID asc") - List findByAddress(String address); + List findByCreatedDateBetweenOrderByBenAddressIDAsc(Timestamp fromDate, Timestamp toDate); - @Transactional - @Modifying - @Query(" UPDATE MBeneficiaryaddress set vanSerialNo = :benAddressID WHERE benAddressID = :benAddressID") - int updateVanSerialNo(@Param("benAddressID") BigInteger benAddressID); + @Query("select a from MBeneficiaryaddress a where a.currAddressValue = :address or " + + "a.emerAddressValue = :address or a.permAddressValue = :address order by a.benAddressID asc") + List findByAddress(String address); - @Query("SELECT benAddressID FROM MBeneficiaryaddress WHERE vanSerialNo =:vanSerialNo AND vanID =:vanID ") - BigInteger findIdByVanSerialNoAndVanID(@Param("vanSerialNo") BigInteger vanSerialNo, @Param("vanID") Integer vanID); + @Transactional + @Modifying + @Query(" UPDATE MBeneficiaryaddress set vanSerialNo = :benAddressID WHERE benAddressID = :benAddressID") + int updateVanSerialNo(@Param("benAddressID") BigInteger benAddressID); + + @Query("SELECT benAddressID FROM MBeneficiaryaddress WHERE vanSerialNo =:vanSerialNo AND vanID =:vanID ") + BigInteger findIdByVanSerialNoAndVanID(@Param("vanSerialNo") BigInteger vanSerialNo, @Param("vanID") Integer vanID); + + @Query("SELECT a FROM MBeneficiaryaddress a WHERE a.vanSerialNo =:vanSerialNo AND a.vanID =:vanID ") + MBeneficiaryaddress getWithVanSerialNoVanID(@Param("vanSerialNo") BigInteger vanSerialNo, + @Param("vanID") Integer vanID); + + /** + * Get user location details from m_userservicerolemapping + */ + @Query(value = "SELECT " + + "ProviderServiceMapID, " + + "Blockid, " + + "Villageid, " + + "WorkingLocationID " + + "FROM db_iemr.m_userservicerolemapping " + + "WHERE UserID = :userId " + + "AND Deleted = false " + + "LIMIT 1", + nativeQuery = true) + List getUserLocation(@Param("userId") Integer userId); - @Query("SELECT a FROM MBeneficiaryaddress a WHERE a.vanSerialNo =:vanSerialNo AND a.vanID =:vanID ") - MBeneficiaryaddress getWithVanSerialNoVanID(@Param("vanSerialNo") BigInteger vanSerialNo, - @Param("vanID") Integer vanID); } diff --git a/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java b/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java index 3a8be24f..4e8bcc01 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenDetailRepo.java @@ -23,6 +23,7 @@ import java.math.BigInteger; import java.sql.Timestamp; +import java.util.Date; import java.util.List; import org.springframework.data.jpa.repository.Modifying; @@ -147,4 +148,237 @@ int untagFamily(@Param("modifiedBy") String modifiedBy, @Param("vanSerialNo") Bi @Query("SELECT b FROM MBeneficiarydetail b WHERE b.familyId =:familyid ") List searchByFamilyId(@Param("familyid") String familyid); + /** + * Find complete beneficiary data by IDs from Elasticsearch + */ + @Query(value = "SELECT " + + "m.BenRegId, " + // 0 + "brm.beneficiaryID, " + // 1 + "d.FirstName, " + // 2 + "d.MiddleName, " + // 3 + "d.LastName, " + // 4 + "d.GenderID, " + // 5 + "g.GenderName, " + // 6 + "d.DOB, " + // 7 + "TIMESTAMPDIFF(YEAR, d.DOB, CURDATE()) as Age, " + // 8 + "d.FatherName, " + // 9 + "d.SpouseName, " + // 10 + "d.MaritalStatusID, " + // 11 + "ms.Status as MaritalStatusName, " + // 12 + "d.IsHIVPositive, " + // 13 + "m.CreatedBy, " + // 14 + "m.CreatedDate, " + // 15 + "UNIX_TIMESTAMP(m.LastModDate) * 1000, " + // 16 + "m.BenAccountID, " + // 17 + "addr.CurrStateId, " + // 18 + "addr.CurrState, " + // 19 + "addr.CurrDistrictId, " + // 20 + "addr.CurrDistrict, " + // 21 + "addr.CurrSubDistrictId, " + // 22 + "addr.CurrSubDistrict, " + // 23 + "addr.CurrPinCode, " + // 24 + "addr.CurrServicePointId, " + // 25 + "addr.CurrServicePoint, " + // 26 + "addr.ParkingPlaceID, " + // 27 + "contact.PreferredPhoneNum, " + // 28 + "addr.CurrVillageId, " + // 29 + "addr.CurrVillage " + // 30 + "FROM i_beneficiarymapping m " + + "LEFT JOIN i_beneficiarydetails d ON m.BenDetailsId = d.BeneficiaryDetailsID " + + "LEFT JOIN m_beneficiaryregidmapping brm ON brm.BenRegId = m.BenRegId " + + "LEFT JOIN db_iemr.m_gender g ON d.GenderID = g.GenderID " + + "LEFT JOIN db_iemr.m_maritalstatus ms ON d.MaritalStatusID = ms.StatusID " + + "LEFT JOIN i_beneficiaryaddress addr ON m.BenAddressId = addr.BenAddressID " + + "LEFT JOIN i_beneficiarycontacts contact ON m.BenContactsId = contact.BenContactsID " + + "WHERE m.BenRegId IN (:ids) AND m.Deleted = false", + nativeQuery = true) + List findCompleteDataByIds(@Param("ids") List ids); + + /** + * Direct search in database (fallback) + */ + @Query(value = "SELECT " + + "m.BenRegId, " + + "brm.beneficiaryID, " + + "d.FirstName, " + + "d.MiddleName, " + + "d.LastName, " + + "d.GenderID, " + + "g.GenderName, " + + "d.DOB, " + + "TIMESTAMPDIFF(YEAR, d.DOB, CURDATE()) as Age, " + + "d.FatherName, " + + "d.SpouseName, " + + "d.MaritalStatusID, " + + "ms.Status as MaritalStatusName, " + + "d.IsHIVPositive, " + + "m.CreatedBy, " + + "m.CreatedDate, " + + "UNIX_TIMESTAMP(m.LastModDate) * 1000, " + + "m.BenAccountID, " + + "addr.CurrStateId, " + + "addr.CurrState, " + + "addr.CurrDistrictId, " + + "addr.CurrDistrict, " + + "addr.CurrSubDistrictId, " + + "addr.CurrSubDistrict, " + + "addr.CurrPinCode, " + + "addr.CurrServicePointId, " + + "addr.CurrServicePoint, " + + "addr.ParkingPlaceID, " + + "contact.PreferredPhoneNum, " + + "addr.CurrVillageId, " + + "addr.CurrVillage " + + "FROM i_beneficiarymapping m " + + "LEFT JOIN i_beneficiarydetails d ON m.BenDetailsId = d.BeneficiaryDetailsID " + + "LEFT JOIN db_iemr.m_gender g ON d.GenderID = g.GenderID " + + "LEFT JOIN m_beneficiaryregidmapping brm ON brm.BenRegId = m.BenRegId " + + "LEFT JOIN db_iemr.m_maritalstatus ms ON d.MaritalStatusID = ms.StatusID " + + "LEFT JOIN i_beneficiaryaddress addr ON m.BenAddressId = addr.BenAddressID " + + "LEFT JOIN i_beneficiarycontacts contact ON m.BenContactsId = contact.BenContactsID " + + "WHERE (d.FirstName LIKE CONCAT('%', :query, '%') " + + " OR d.MiddleName LIKE CONCAT('%', :query, '%') " + + " OR d.LastName LIKE CONCAT('%', :query, '%') " + + " OR d.FatherName LIKE CONCAT('%', :query, '%') " + + " OR d.BeneficiaryRegID = :query " + + " OR contact.PreferredPhoneNum = :query " + + " OR contact.PhoneNum1 = :query " + + " OR contact.PhoneNum2 = :query " + + " OR contact.PhoneNum3 = :query " + + " OR contact.PhoneNum4 = :query " + + " OR contact.PhoneNum5 = :query) " + + "AND m.Deleted = false " + + "LIMIT 20", + nativeQuery = true) + List searchBeneficiaries(@Param("query") String query); + + /** + * Get all phone numbers for a beneficiary + */ + @Query(value = "SELECT " + + "contact.PreferredPhoneNum as phoneNo, " + + "'Preferred' as phoneType, " + + "1 as priority " + + "FROM i_beneficiarymapping m " + + "LEFT JOIN i_beneficiarycontacts contact ON m.BenContactsId = contact.BenContactsID " + + "WHERE m.BenRegId = :beneficiaryId AND contact.PreferredPhoneNum IS NOT NULL " + + "UNION ALL " + + "SELECT contact.PhoneNum1, contact.PhoneTyp1, 2 " + + "FROM i_beneficiarymapping m " + + "LEFT JOIN i_beneficiarycontacts contact ON m.BenContactsId = contact.BenContactsID " + + "WHERE m.BenRegId = :beneficiaryId AND contact.PhoneNum1 IS NOT NULL " + + "UNION ALL " + + "SELECT contact.PhoneNum2, contact.PhoneTyp2, 3 " + + "FROM i_beneficiarymapping m " + + "LEFT JOIN i_beneficiarycontacts contact ON m.BenContactsId = contact.BenContactsID " + + "WHERE m.BenRegId = :beneficiaryId AND contact.PhoneNum2 IS NOT NULL " + + "UNION ALL " + + "SELECT contact.PhoneNum3, contact.PhoneTyp3, 4 " + + "FROM i_beneficiarymapping m " + + "LEFT JOIN i_beneficiarycontacts contact ON m.BenContactsId = contact.BenContactsID " + + "WHERE m.BenRegId = :beneficiaryId AND contact.PhoneNum3 IS NOT NULL " + + "UNION ALL " + + "SELECT contact.PhoneNum4, contact.PhoneTyp4, 5 " + + "FROM i_beneficiarymapping m " + + "LEFT JOIN i_beneficiarycontacts contact ON m.BenContactsId = contact.BenContactsID " + + "WHERE m.BenRegId = :beneficiaryId AND contact.PhoneNum4 IS NOT NULL " + + "UNION ALL " + + "SELECT contact.PhoneNum5, contact.PhoneTyp5, 6 " + + "FROM i_beneficiarymapping m " + + "LEFT JOIN i_beneficiarycontacts contact ON m.BenContactsId = contact.BenContactsID " + + "WHERE m.BenRegId = :beneficiaryId AND contact.PhoneNum5 IS NOT NULL " + + "ORDER BY priority", + nativeQuery = true) + List findPhoneNumbersByBeneficiaryId(@Param("beneficiaryId") Long beneficiaryId); + +// Advance Search ES +@Query(value = + "SELECT DISTINCT " + + "m.BenRegId, " + // 0 + "brm.beneficiaryID, " + // 1 + "d.FirstName, " + // 2 + "d.MiddleName, " + // 3 + "d.LastName, " + // 4 + "d.GenderID, " + // 5 + "g.GenderName, " + // 6 + "d.DOB, " + // 7 + "TIMESTAMPDIFF(YEAR, d.DOB, CURDATE()) AS Age, "+ + "d.FatherName, " + // 9 + "d.SpouseName, " + // 10 + "d.MaritalStatusID, " + // 11 + "ms.Status AS MaritalStatusName, " + // 12 + "d.IsHIVPositive, " + // 13 + "m.CreatedBy, " + // 14 + "m.CreatedDate, " + // 15 + "UNIX_TIMESTAMP(m.LastModDate) * 1000, " + // 16 + "m.BenAccountID, " + // 17 + "addr.CurrStateId, " + // 18 + "addr.CurrState, " + // 19 + "addr.CurrDistrictId, " + // 20 + "addr.CurrDistrict, " + // 21 + "addr.CurrSubDistrictId, " + // 22 + "addr.CurrSubDistrict, " + // 23 + "addr.CurrPinCode, " + // 24 + "addr.CurrServicePointId, " + // 25 + "addr.CurrServicePoint, " + // 26 + "addr.ParkingPlaceID, " + // 27 + "contact.PreferredPhoneNum, " + // 28 + "addr.CurrVillageId, " + + "addr.CurrVillage " + + "FROM i_beneficiarymapping m " + + "LEFT JOIN i_beneficiarydetails d " + + " ON m.BenDetailsId = d.BeneficiaryDetailsID " + + "LEFT JOIN db_iemr.m_gender g " + + " ON d.GenderID = g.GenderID " + + "LEFT JOIN db_iemr.m_maritalstatus ms ON d.MaritalStatusID = ms.StatusID " + + "LEFT JOIN i_beneficiaryaddress addr " + + " ON m.BenAddressId = addr.BenAddressID " + + "LEFT JOIN i_beneficiarycontacts contact " + + " ON m.BenContactsId = contact.BenContactsID " + + "LEFT JOIN m_beneficiaryregidmapping brm ON brm.BenRegId = m.BenRegId " + + "WHERE m.Deleted = false " + + + "AND (:firstName IS NULL OR d.FirstName LIKE CONCAT('%', :firstName, '%')) " + + "AND (:middleName IS NULL OR d.MiddleName LIKE CONCAT('%', :middleName, '%')) " + + "AND (:lastName IS NULL OR d.LastName LIKE CONCAT('%', :lastName, '%')) " + + "AND (:genderId IS NULL OR d.GenderID = :genderId) " + + "AND (:dob IS NULL OR DATE(d.DOB) = DATE(:dob)) " + + + "AND (:stateId IS NULL OR addr.CurrStateId = :stateId) " + + "AND (:districtId IS NULL OR addr.CurrDistrictId = :districtId) " + + "AND (:blockId IS NULL OR addr.CurrSubDistrictId = :blockId) " + + + "AND (:fatherName IS NULL OR d.FatherName LIKE CONCAT('%', :fatherName, '%')) " + + "AND (:spouseName IS NULL OR d.SpouseName LIKE CONCAT('%', :spouseName, '%')) " + + "AND (:maritalStatus IS NULL OR d.MaritalStatusID = :maritalStatus) " + + "AND (:phoneNumber IS NULL OR " + + " contact.PreferredPhoneNum LIKE CONCAT('%', :phoneNumber, '%') OR " + + " contact.PhoneNum1 LIKE CONCAT('%', :phoneNumber, '%') OR " + + " contact.PhoneNum2 LIKE CONCAT('%', :phoneNumber, '%') OR " + + " contact.PhoneNum3 LIKE CONCAT('%', :phoneNumber, '%') OR " + + " contact.PhoneNum4 LIKE CONCAT('%', :phoneNumber, '%') OR " + + " contact.PhoneNum5 LIKE CONCAT('%', :phoneNumber, '%')) " + + + "AND (:beneficiaryId IS NULL OR d.BeneficiaryRegID = :beneficiaryId) " + + + "ORDER BY m.CreatedDate DESC " + + "LIMIT 100", + nativeQuery = true) +List advancedSearchBeneficiaries( + @Param("firstName") String firstName, + @Param("middleName") String middleName, + @Param("lastName") String lastName, + @Param("genderId") Integer genderId, + @Param("dob") Date dob, + @Param("stateId") Integer stateId, + @Param("districtId") Integer districtId, + @Param("blockId") Integer blockId, + @Param("fatherName") String fatherName, + @Param("spouseName") String spouseName, + @Param("maritalStatus") String maritalStatus, + @Param("phoneNumber") String phoneNumber, + @Param("beneficiaryId") String beneficiaryId +); + + } diff --git a/src/main/java/com/iemr/common/identity/repo/BenMappingRepo.java b/src/main/java/com/iemr/common/identity/repo/BenMappingRepo.java index e3080a5d..fd781922 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenMappingRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenMappingRepo.java @@ -142,4 +142,106 @@ Long getBeneficiaryCountsByVillageIDAndLastModifyDate(@Param("villageIDs") List< @Query("SELECT a FROM MBeneficiarymapping a WHERE a.vanSerialNo =:vanSerialNo AND a.vanID =:vanID ") MBeneficiarymapping getWithVanSerialNoVanID(@Param("vanSerialNo") BigInteger vanSerialNo, @Param("vanID") Integer vanID); + + @Query("SELECT b.benRegId, b.benMapId FROM MBeneficiarymapping b WHERE benRegId IS NOT NULL AND deleted = false") + List getAllBeneficiaryIds(); + + /** + * Get beneficiary IDs in batches for efficient processing + */ + @Query(value = "SELECT BenRegId FROM i_beneficiarymapping " + + "WHERE Deleted = false ORDER BY BenRegId LIMIT :limit OFFSET :offset", + nativeQuery = true) + List getBeneficiaryIdsBatch(@Param("offset") int offset, @Param("limit") int limit); + + /** + * Count total non-deleted beneficiaries + */ + @Query(value = "SELECT COUNT(*) FROM i_beneficiarymapping WHERE Deleted = false", nativeQuery = true) + long countActiveBeneficiaries(); + /** + * COMPLETE DATA FETCH - Single query with all joins + * This is the key query that fetches everything needed for ES indexing + */ + @Query(value = "SELECT " + + "m.BenRegId, " + // 0 + "brm.BeneficiaryId, " + // 1 + "d.FirstName, " + // 2 + "d.MiddleName, " + // 3 + "d.LastName, " + // 4 + "d.GenderID, " + // 5 + "g.GenderName, " + // 6 + "d.DOB, " + // 7 + "TIMESTAMPDIFF(YEAR, d.DOB, CURDATE()), " + // 8 - age + "d.FatherName, " + // 9 + "d.SpouseName, " + // 10 + "d.MaritalStatusID, " + // 11 + "ms.Status as MaritalStatusName, " + // 12 - MaritalStatusName + "d.IsHIVPositive, " + // 13 + "m.CreatedBy, " + // 14 + "m.CreatedDate, " + // 15 + "UNIX_TIMESTAMP(m.LastModDate) * 1000, " + // 16 + "m.BenAccountID, " + // 17 + "contact.PreferredPhoneNum, " + // 18 + "fam.BenFamilyMapId, " + // 19 + "addr.CurrStateId, " + // 20 + "addr.CurrState, " + // 21 + "addr.CurrDistrictId, " + // 22 + "addr.CurrDistrict, " + // 23 + "addr.CurrSubDistrictId, " + // 24 + "addr.CurrSubDistrict, " + // 25 + "addr.CurrVillageId, " + // 26 + "addr.CurrVillage, " + // 27 + "addr.CurrPinCode, " + // 28 + "addr.CurrServicePointId, " + // 29 + "addr.CurrServicePoint, " + // 30 + "addr.ParkingPlaceID, " + // 31 + "addr.PermStateId, " + // 32 + "addr.PermState, " + // 33 + "addr.PermDistrictId, " + // 34 + "addr.PermDistrict, " + // 35 + "addr.PermSubDistrictId, " + // 36 + "addr.PermSubDistrict, " + // 37 + "addr.PermVillageId, " + // 38 + "addr.PermVillage " + // 39 + "FROM i_beneficiarymapping m " + + "LEFT JOIN i_beneficiarydetails d ON m.BenDetailsId = d.BeneficiaryDetailsID " + + "LEFT JOIN db_iemr.m_gender g ON d.GenderID = g.GenderID " + + "LEFT JOIN db_iemr.m_maritalstatus ms ON d.MaritalStatusID = ms.MaritalStatusID " + + "LEFT JOIN i_beneficiaryaddress addr ON m.BenAddressId = addr.BenAddressID " + + "LEFT JOIN i_beneficiarycontacts contact ON m.BenContactsId = contact.BenContactsID " + + "LEFT JOIN m_beneficiaryregidmapping brm ON brm.BenRegId = m.BenRegId " + + "LEFT JOIN db_iemr.m_benhealthidmapping h ON m.BenRegId = h.BeneficiaryRegID " + + "LEFT JOIN i_beneficiaryfamilymapping fam " + + " ON m.BenRegId = fam.AssociatedBenRegID " + + " AND fam.Deleted = false " + + "WHERE m.BenRegId IN :benRegIds " + + "AND m.Deleted = false", + nativeQuery = true) + + List findCompleteDataByBenRegIds(@Param("benRegIds") List benRegIds); + + /** + * Find with all details (JPA approach - for single beneficiary) + */ + @Query("SELECT m FROM MBeneficiarymapping m " + + "LEFT JOIN FETCH m.mBeneficiarydetail " + + "LEFT JOIN FETCH m.mBeneficiarycontact " + + "LEFT JOIN FETCH m.mBeneficiaryaddress " + + "WHERE m.benRegId = :benRegId AND m.deleted = false") + MBeneficiarymapping findByBenRegIdWithDetails(@Param("benRegId") BigInteger benRegId); + + /** + * Simple find by benRegId + */ + @Query("SELECT m FROM MBeneficiarymapping m WHERE m.benRegId = :benRegId AND m.deleted = false") + MBeneficiarymapping findByBenRegId(@Param("benRegId") BigInteger benRegId); + + /** + * Check if beneficiary exists + */ + @Query(value = "SELECT COUNT(*) > 0 FROM i_beneficiarymapping WHERE BenRegId = :benRegId AND Deleted = false", nativeQuery = true) + boolean existsByBenRegId(@Param("benRegId") BigInteger benRegId); + + } diff --git a/src/main/java/com/iemr/common/identity/repo/V_BenAdvanceSearchRepo.java b/src/main/java/com/iemr/common/identity/repo/V_BenAdvanceSearchRepo.java index 0be61bb0..a7fd5f27 100644 --- a/src/main/java/com/iemr/common/identity/repo/V_BenAdvanceSearchRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/V_BenAdvanceSearchRepo.java @@ -46,4 +46,9 @@ public interface V_BenAdvanceSearchRepo extends CrudRepository getBenRegIDByHealthIDNoAbhaIdNo(@Param("healthIDNo") String healthIDNo); + //Batch fetch ABHA details for multiple beneficiaries + @Query(nativeQuery = true, value = "SELECT BeneficiaryRegID, HealthID, HealthIDNumber, AuthenticationMode, CreatedDate" + + " FROM db_iemr.m_benhealthidmapping WHERE BeneficiaryRegID IN :benRegIDs ORDER BY BeneficiaryRegID, CreatedDate DESC") + List getBenAbhaDetailsByBenRegIDs(@Param("benRegIDs") List benRegIDs); + } diff --git a/src/main/java/com/iemr/common/identity/repo/elasticsearch/SyncJobRepo.java b/src/main/java/com/iemr/common/identity/repo/elasticsearch/SyncJobRepo.java new file mode 100644 index 00000000..62f9eadb --- /dev/null +++ b/src/main/java/com/iemr/common/identity/repo/elasticsearch/SyncJobRepo.java @@ -0,0 +1,65 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.repo.elasticsearch; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import com.iemr.common.identity.data.elasticsearch.ElasticsearchSyncJob; + +@Repository +public interface SyncJobRepo extends JpaRepository { + + /** + * Find all active (running or pending) jobs + */ + @Query("SELECT j FROM ElasticsearchSyncJob j WHERE j.status IN ('RUNNING', 'PENDING') ORDER BY j.createdDate DESC") + List findActiveJobs(); + + /** + * Check if there's any active full sync job + */ + @Query("SELECT COUNT(j) > 0 FROM ElasticsearchSyncJob j WHERE j.jobType = 'FULL_SYNC' AND j.status IN ('RUNNING', 'PENDING')") + boolean hasActiveFullSyncJob(); + + /** + * Find latest job of a specific type + */ + @Query("SELECT j FROM ElasticsearchSyncJob j WHERE j.jobType = :jobType ORDER BY j.createdDate DESC") + List findLatestJobsByType(String jobType); + + /** + * Find recent jobs (last 10) + */ + @Query("SELECT j FROM ElasticsearchSyncJob j ORDER BY j.createdDate DESC") + List findRecentJobs(); + + /** + * Find job by ID + */ + Optional findByJobId(Long jobId); +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/service/IdentityService.java b/src/main/java/com/iemr/common/identity/service/IdentityService.java index 029b60a4..08b9fbbe 100644 --- a/src/main/java/com/iemr/common/identity/service/IdentityService.java +++ b/src/main/java/com/iemr/common/identity/service/IdentityService.java @@ -18,7 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see https://www.gnu.org/licenses/. -*/ + */ package com.iemr.common.identity.service; import java.math.BigDecimal; @@ -32,6 +32,7 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; +import java.util.stream.Collectors; import javax.sql.DataSource; @@ -49,6 +50,7 @@ import com.google.gson.JsonParser; import com.google.gson.LongSerializationPolicy; import com.iemr.common.identity.data.rmnch.RMNCHBeneficiaryDetailsRmnch; +import com.iemr.common.identity.domain.Address; import com.iemr.common.identity.domain.MBeneficiaryAccount; import com.iemr.common.identity.domain.MBeneficiaryImage; import com.iemr.common.identity.domain.MBeneficiaryaddress; @@ -90,673 +92,954 @@ import com.iemr.common.identity.repo.MBeneficiaryImageRepo; import com.iemr.common.identity.repo.V_BenAdvanceSearchRepo; import com.iemr.common.identity.repo.rmnch.RMNCHBeneficiaryDetailsRmnchRepo; +import com.iemr.common.identity.service.elasticsearch.ElasticsearchService; import com.iemr.common.identity.utils.mapper.OutputMapper; import com.iemr.common.identity.utils.response.OutputResponse; +import org.springframework.scheduling.annotation.Async; +import org.springframework.beans.factory.annotation.Value; + +import com.iemr.common.identity.service.elasticsearch.BeneficiaryElasticsearchIndexUpdater; import jakarta.persistence.NoResultException; import jakarta.persistence.QueryTimeoutException; - @Service public class IdentityService { - private static final Logger logger = LoggerFactory.getLogger(IdentityService.class); - public static final String CREATED_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; - @Autowired - private DataSource dataSource; - - private JdbcTemplate jdbcTemplate; - - private JdbcTemplate getJdbcTemplate() { - return new JdbcTemplate(dataSource); - - } - - @Autowired - private RMNCHBeneficiaryDetailsRmnchRepo rMNCHBeneficiaryDetailsRmnchRepo; - - @Autowired - IdentityMapper mapper; - @Autowired - IdentityEditMapper editMapper; - @Autowired - IdentitySearchMapper searchMapper; - @Autowired - private IdentityPartialMapper partialMapper; - @Autowired - BenAddressRepo addressRepo; - @Autowired - BenConsentRepo consentRepo; - @Autowired - BenContactRepo contactRepo; - @Autowired - BenDataAccessRepo accessRepo; - @Autowired - BenDetailRepo detailRepo; - @Autowired - BenFamilyMappingRepo familyMapRepo; - @Autowired - BenIdentityRepo identityRepo; - @Autowired - BenMappingRepo mappingRepo; - @Autowired - BenRegIdMappingRepo regIdRepo; - @Autowired - BenServiceMappingRepo serviceMapRepo; - @Autowired - MBeneficiaryAccountRepo accountRepo; - @Autowired - MBeneficiaryImageRepo imageRepo; - @Autowired - private BenIdImportMapper benIdImportMapper; - @Autowired - private V_BenAdvanceSearchRepo v_BenAdvanceSearchRepo; - - public void getBenAdress() { - logger.debug("Address count: " + addressRepo.count()); - logger.debug( - "Address By Address: " + addressRepo.findByBenAddressIDOrderByBenAddressIDAsc(new BigInteger("12"))); - logger.debug("Consent count: " + consentRepo.count()); - logger.debug("Consent By Id: " + consentRepo.findByBenConsentIDOrderByBenConsentIDAsc(new BigInteger("12"))); - logger.debug("Contact count: " + contactRepo.count()); - logger.debug("Contact By Id: " + contactRepo.findByBenContactsIDOrderByBenContactsIDAsc(new BigInteger("12"))); - logger.debug("Data Access count: " + accessRepo.count()); - logger.debug("Data Access By Id: " + accessRepo.findByAccessIdOrderByAccessIdAsc(new BigInteger("12"))); - logger.debug("Detail count: " + detailRepo.count()); - logger.debug("Detail By Address: " - + detailRepo.findByBeneficiaryDetailsIdOrderByBeneficiaryDetailsIdAsc(new BigInteger("12"))); - logger.debug("FamilyMap count: " + familyMapRepo.count()); - logger.debug( - "FamilyMap By Id: " + familyMapRepo.findByBenFamilyMapIdOrderByBenFamilyMapIdAsc(new BigInteger("12"))); - logger.debug("Identity count: " + identityRepo.count()); - logger.debug("Identity By Id: " + identityRepo.findByBenIdentityId(new BigInteger("12"))); - logger.debug("Mapping count: " + mappingRepo.count()); - logger.debug("Mapping By Id: " + mappingRepo.findByBenMapIdOrderByBenMapIdAsc(new BigInteger("12"))); - } - - /** - * - * @param searchDTO - * @return - */ - public List getBeneficiaries(IdentitySearchDTO searchDTO) - throws NoResultException, QueryTimeoutException, Exception { - List list = new ArrayList(); - - /** - * if beneficiary Id present - */ - if (searchDTO.getBeneficiaryId() != null) { - logger.info("getting beneficiaries by ID for " + searchDTO.getBeneficiaryId()); - return this.getBeneficiariesByBenId(searchDTO.getBeneficiaryId()); - } - - /** - * if beneficiary Reg Id present - */ - if (searchDTO.getBeneficiaryRegId() != null) { - logger.info("getting beneficiaries by reg ID for " + searchDTO.getBeneficiaryRegId()); - return this.getBeneficiariesByBenRegId(searchDTO.getBeneficiaryRegId()); - } - - /** - * if beneficiary Reg Id present - */ - if (searchDTO.getContactNumber() != null) { - logger.info("getting beneficiaries by contact no for " + searchDTO.getContactNumber()); - List list3 = this.getBeneficiariesByPhoneNum(searchDTO.getContactNumber()); - if (!list3.isEmpty() && searchDTO.getIsD2D() != null && Boolean.TRUE.equals(searchDTO.getIsD2D())) { - - for (int i = 0; i < list3.size(); i++) { - if (searchDTO.getFirstName() != null) { - if (list3.get(i) == null || list3.get(i).getBeneficiaryDetails() == null - || list3.get(i).getBeneficiaryDetails().getFirstName() == null - || !list3.get(i).getBeneficiaryDetails().getFirstName() - .equalsIgnoreCase(searchDTO.getFirstName())) { - list3.remove(i); - i--; - - continue; - } - - } - if (searchDTO.getLastName() != null) { - if (list3.get(i) == null || list3.get(i).getBeneficiaryDetails() == null - || list3.get(i).getBeneficiaryDetails().getLastName() == null - || !list3.get(i).getBeneficiaryDetails().getLastName() - .equalsIgnoreCase(searchDTO.getLastName())) { - list3.remove(i); - i--; - - continue; - } - } - - if (searchDTO.getGenderId() != null) { - if (list3.get(i) == null || list3.get(i).getBeneficiaryDetails() == null - || list3.get(i).getBeneficiaryDetails().getGenderId() == null || !list3.get(i) - .getBeneficiaryDetails().getGenderId().equals(searchDTO.getGenderId())) { - - list3.remove(i); - i--; - continue; - } - } - if (searchDTO.getHouseHoldID() != null) { - if (list3.get(i) == null || list3.get(i).getBeneficiaryDetails() == null - || list3.get(i).getBeneficiaryDetails().getHouseHoldID() == null || !list3.get(i) - .getBeneficiaryDetails().getHouseHoldID().equals(searchDTO.getHouseHoldID())) { - - list3.remove(i); - i--; - continue; - } - } - - if (searchDTO.getCurrentAddress().getStateId() != null) { - if (list3.get(i) == null || list3.get(i).getCurrentAddress() == null - || list3.get(i).getCurrentAddress().getStateId() == null - || !list3.get(i).getCurrentAddress().getStateId() - .equals(searchDTO.getCurrentAddress().getStateId())) { - - list3.remove(i); - i--; - continue; - } - } - - if (searchDTO.getCurrentAddress().getDistrictId() != null) { - if (list3.get(i) == null || list3.get(i).getCurrentAddress() == null - || list3.get(i).getCurrentAddress().getDistrictId() == null - || !list3.get(i).getCurrentAddress().getDistrictId() - .equals(searchDTO.getCurrentAddress().getDistrictId())) { - - list3.remove(i); - i--; - continue; - } - } - - if (searchDTO.getCurrentAddress().getVillageId() != null) { - if (list3.get(i) == null || list3.get(i).getCurrentAddress() == null - || list3.get(i).getCurrentAddress().getVillageId() == null - || !list3.get(i).getCurrentAddress().getVillageId() - .equals(searchDTO.getCurrentAddress().getVillageId())) { - - list3.remove(i); - i--; - continue; - } - } - - } - } - return list3; - } - - /** - * New logic for advance search, 03-10-2018 - */ - List tmpList = mappingRepo.dynamicFilterSearchNew(searchDTO); - for (VBenAdvanceSearch obj : tmpList) { - list.add(this.getBeneficiariesDTO(this.getBeneficiariesDTONew1(obj))); - logger.debug("benMapId: " + obj.getBenMapID()); - } - /** - * End - */ - - return list; - } - - /** - * - * Check which parameters available Get BenMapID based on the parameter/set of - * parameters Use BenMapID to retrieve all data for a Beneficiary - * - * @param benId - * @return - */ - public List getBeneficiariesByBenId(BigInteger benId) - throws NoResultException, QueryTimeoutException, Exception { - logger.info("IdentityService.getBeneficiariesByBenId - start, beneficiaryID : " + benId); - List list = new ArrayList(); - - MBeneficiaryregidmapping regId = regIdRepo.findByBeneficiaryID(benId); - if (regId != null && regId.getBenRegId() != null) { - List benMapObjArr = mappingRepo.getBenMappingByRegID(regId.getBenRegId()); - - // new logic, 27-08-2018 - if (benMapObjArr != null && benMapObjArr.size() > 0) { - MBeneficiarymapping benMap = this.getBeneficiariesDTONew(benMapObjArr.get(0)); - list.add(this.getBeneficiariesDTO(benMap)); - } - } - logger.info("benMap size " + (list.size() == 0 ? "No Beneficiary Found" : list.size())); - // end new logic - - logger.info("IdentityService.getBeneficiariesByBenId - end - beneficiaryID : " + benId); - - return list; - } - - /** - * - * @param BenRegId - * @return - */ - public List getBeneficiariesByBenRegId(BigInteger benRegId) - throws NoResultException, QueryTimeoutException, Exception { - List list = new ArrayList(); - logger.info("IdentityService.getBeneficiariesByBenRegId - start for benRegId " + benRegId); - try { - // new logic, 27-09-2018 - List benMapObjArr = mappingRepo.getBenMappingByRegID(benRegId); - - // new logic, 27-08-2018 - if (benMapObjArr != null && !benMapObjArr.isEmpty()) { - MBeneficiarymapping benMap = this.getBeneficiariesDTONew(benMapObjArr.get(0)); - list.add(this.getBeneficiariesDTO(benMap)); - } - logger.info("benMap size" + (list.isEmpty() ? "No Beneficiary Found" : list.size())); - // end new logic - - logger.info("IdentityService.getBeneficiariesByBenRegId - end for benRegId " + benRegId); - } catch (Exception e) { - logger.error("error in beneficiary search for beneficiary reg id : " + benRegId + " error : " - + e.getLocalizedMessage()); - } - - return list; - } - - /** - * - * Check which parameters available Get BenMapID based on the parameter/set of - * parameters Use BenMapID to retrieve all data for a Beneficiary - * - * @param phoneNum - * @return - */ - - public List getBeneficiariesByPhoneNum(String phoneNum) - throws NoResultException, QueryTimeoutException { - // new logic, 27-09-2018 - List list = new ArrayList<>(); - try { - List benContact = contactRepo.findByAnyPhoneNum(phoneNum); - - logger.info(benContact.size() + " contacts found for phone number " + phoneNum); - - List benMapObjArr = new ArrayList<>(); - - for (MBeneficiarycontact benContactOBJ : benContact) { - benMapObjArr.addAll(mappingRepo.getBenMappingByBenContactIdListNew(benContactOBJ.getVanSerialNo(), - benContactOBJ.getVanID())); - } - - for (Object[] benMapOBJ : benMapObjArr) { - list.add(this.getBeneficiariesDTO(this.getBeneficiariesDTONew(benMapOBJ))); - } - - } catch (Exception e) { - logger.error( - "error in beneficiary search for phone no : " + phoneNum + " error : " + e.getLocalizedMessage()); - } - - logger.info("IdentityService.getBeneficiariesByPhoneNum - end"); - // end - return list; - } - - /*** - * - * Search beneficiary by healthID / ABHA address - * - * @param healthID - * @return - * @throws NoResultException - * @throws QueryTimeoutException - * @throws Exception - */ - - public List getBeneficiaryByHealthIDAbhaAddress(String healthID) - throws NoResultException, QueryTimeoutException, Exception { - List beneficiaryList = new ArrayList<>(); - try { - List regIDList = v_BenAdvanceSearchRepo.getBenRegIDByHealthIDAbhaAddress(healthID); - if (regIDList != null && !regIDList.isEmpty()) { - for (BigInteger benRegID : regIDList) { - if (benRegID != null) { - List searchList = this.getBeneficiariesByBenRegId(benRegID); - beneficiaryList.addAll(searchList); - } - } - } - } catch (Exception e) { - logger.error("error in beneficiary search for health ID / ABHA Address : " + healthID + " error : " - + e.getLocalizedMessage()); - } - return beneficiaryList; - } - - /*** - * - * Search beneficiary by healthIDNo / ABHA ID No - * - * @param healthIDNo - * @return - * @throws NoResultException - * @throws QueryTimeoutException - * @throws Exception - */ - - public List getBeneficiaryByHealthIDNoAbhaIdNo(String healthIDNo) - throws NoResultException, QueryTimeoutException, Exception { - List beneficiaryList = new ArrayList<>(); - try { - List regIDList = v_BenAdvanceSearchRepo.getBenRegIDByHealthIDNoAbhaIdNo(healthIDNo); - if (regIDList != null && !regIDList.isEmpty()) { - for (BigInteger benRegID : regIDList) { - if (benRegID != null) { - List searchList = this.getBeneficiariesByBenRegId(benRegID); - beneficiaryList.addAll(searchList); - } - } - } - } catch (Exception e) { - logger.error("error in beneficiary search for health ID No / ABHA ID No : " + healthIDNo + " error : " - + e.getLocalizedMessage()); - } - return beneficiaryList; - } - - public List searhBeneficiaryByFamilyId(String familyId) - throws NoResultException, QueryTimeoutException { - List beneficiaryList = new ArrayList<>(); - try { - - // find benmap ids - List benMapObjArr = null; - List benDetailsVanSerialNoList = new ArrayList<>(); - int vanID = 0; - - List benDetailsList = detailRepo.searchByFamilyId(familyId); - - if (benDetailsList == null || benDetailsList.isEmpty()) - return beneficiaryList; - else { - // considering as of now family creation is possible through facility modules - // only - vanID = benDetailsList.get(0).getVanID(); - - for (MBeneficiarydetail benDetails : benDetailsList) { - benDetailsVanSerialNoList.add(benDetails.getVanSerialNo()); - } - - benMapObjArr = mappingRepo.getBenMappingByBenDetailsIds(benDetailsVanSerialNoList, vanID); - - for (Object[] benMapOBJ : benMapObjArr) { - beneficiaryList.add(this.getBeneficiariesDTO(this.getBeneficiariesDTONew(benMapOBJ))); - } - - } - - } catch (Exception e) { - logger.error( - "error in beneficiary search for familyId : " + familyId + " error : " + e.getLocalizedMessage()); - } - return beneficiaryList; - } - - public List searchBeneficiaryByVillageIdAndLastModifyDate(List villageIDs, - Timestamp lastModifiedDate) { - - List beneficiaryList = new ArrayList<>(); - try { - // find benmap ids - List benMappingsList = mappingRepo - .findByBeneficiaryDetailsByVillageIDAndLastModifyDate(villageIDs, lastModifiedDate); - if (benMappingsList != null && !benMappingsList.isEmpty()) { - for (MBeneficiarymapping benMapOBJ : benMappingsList) { - beneficiaryList.add(this.getBeneficiariesDTO(benMapOBJ)); - } - } - - } catch (Exception e) { - logger.error("error in beneficiary search to sync to CHO App with villageIDs: {} ", - villageIDs + " error : " + e.getLocalizedMessage()); - } - return beneficiaryList; - } - - public Long countBeneficiaryByVillageIdAndLastModifyDate(List villageIDs, Timestamp lastModifiedDate) { - Long beneficiaryCount = 0L; - try { - beneficiaryCount = mappingRepo.getBeneficiaryCountsByVillageIDAndLastModifyDate(villageIDs, - lastModifiedDate); - } catch (Exception e) { - logger.error("error in getting beneficiary count to sync to CHO App with villageIDs: {},error :{} ", - villageIDs, e.getLocalizedMessage()); - } - return beneficiaryCount; - } - - public List searhBeneficiaryByGovIdentity(String identity) - throws NoResultException, QueryTimeoutException { - List beneficiaryList = new ArrayList<>(); - try { - - List benMapObjArr = new ArrayList<>(); - - // find identity no - List benIdentityList = identityRepo.searchByIdentityNo(identity); - - // find benmap ids - if (benIdentityList == null || benIdentityList.isEmpty()) - return beneficiaryList; - else { - for (MBeneficiaryidentity identityObj : benIdentityList) { - benMapObjArr.addAll( - mappingRepo.getBenMappingByVanSerialNo(identityObj.getBenMapId(), identityObj.getVanID())); - } - - for (Object[] benMapOBJ : benMapObjArr) { - beneficiaryList.add(this.getBeneficiariesDTO(this.getBeneficiariesDTONew(benMapOBJ))); - } - - } - - } catch (Exception e) { - logger.error("error in beneficiary search for gov identity : " + identity + " error : " - + e.getLocalizedMessage()); - } - return beneficiaryList; - } - - private MBeneficiarymapping getBeneficiariesDTONew(Object[] benMapArr) { - MBeneficiarymapping mapping = new MBeneficiarymapping(); - if (benMapArr != null && benMapArr.length == 12 && benMapArr[8] != null && benMapArr[9] != null) { - mapping.setBenMapId(getBigIntegerValueFromObject(benMapArr[0])); - mapping.setCreatedBy(String.valueOf(benMapArr[10])); - mapping.setCreatedDate((Timestamp) benMapArr[11]); - mapping = mappingRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[9]), (Integer) benMapArr[8]); - MBeneficiaryaddress address = addressRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[1]), (Integer) benMapArr[8]); - MBeneficiaryconsent consent = consentRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[2]), (Integer) benMapArr[8]); - MBeneficiarycontact contact = contactRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[3]), (Integer) benMapArr[8]); - MBeneficiarydetail details = detailRepo.getWith_vanSerialNo_vanID(getBigIntegerValueFromObject(benMapArr[4]), (Integer) benMapArr[8]); - MBeneficiaryregidmapping regidmapping = regIdRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[5]), (Integer) benMapArr[8]); - MBeneficiaryAccount account = accountRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[7]), (Integer) benMapArr[8]); - MBeneficiaryImage image = imageRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[6]), (Integer) benMapArr[8]); - List servicemap = serviceMapRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[0]), (Integer) benMapArr[8]); - List identity = identityRepo.findByBenMapIdAndVanID(getBigIntegerValueFromObject(benMapArr[0]), (Integer) benMapArr[8]); - List familymapping = familyMapRepo.findByBenMapIdAndVanIDOrderByBenFamilyMapIdAsc(getBigIntegerValueFromObject(benMapArr[0]), (Integer) benMapArr[8]); - - mapping.setMBeneficiaryaddress(address); - mapping.setMBeneficiaryconsent(consent); - mapping.setMBeneficiarycontact(contact); - mapping.setMBeneficiarydetail(details); - mapping.setMBeneficiaryfamilymappings(familymapping); - mapping.setMBeneficiaryidentities(identity); - mapping.setMBeneficiaryImage(image); - mapping.setMBeneficiaryregidmapping(regidmapping); - mapping.setMBeneficiaryservicemappings(servicemap); - mapping.setMBeneficiaryAccount(account); - //benMapOBJ = mappingRepo.getMapping(getBigIntegerValueFromObject(benMapArr[9]), (Integer) benMapArr[8]); - - - - BigInteger benRegId = new BigInteger(benMapArr[5].toString()); - RMNCHBeneficiaryDetailsRmnch obj = rMNCHBeneficiaryDetailsRmnchRepo - .getByRegID(benRegId); - - if (obj != null) { - if (obj.getHouseoldId() != null) - mapping.setHouseHoldID(obj.getHouseoldId()); - if (obj.getGuidelineId() != null) - mapping.setGuideLineID(obj.getGuidelineId()); - if (obj.getRchid() != null) - mapping.setRchID(obj.getRchid()); - } - - } - return mapping; - } - private MBeneficiarymapping getBeneficiariesDTONewPartial(Object[] benMapArr) { - MBeneficiarymapping benMapOBJ = new MBeneficiarymapping(); - - benMapOBJ.setBenMapId(getBigIntegerValueFromObject(benMapArr[0])); - benMapOBJ.setCreatedBy(String.valueOf(benMapArr[10])); - benMapOBJ.setCreatedDate((Timestamp) benMapArr[11]); - - if (benMapArr.length == 12 && benMapArr[8] != null && benMapArr[9] != null) { - - benMapOBJ.setMBeneficiarydetail(detailRepo - .getWith_vanSerialNo_vanID(getBigIntegerValueFromObject(benMapArr[4]), (Integer) benMapArr[8])); - benMapOBJ.setMBeneficiaryregidmapping(regIdRepo - .getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[5]), (Integer) benMapArr[8])); - } - return benMapOBJ; - } - - // 03-10-2018 - // get ben mapping object from v_benadvancesearch - private MBeneficiarymapping getBeneficiariesDTONew1(VBenAdvanceSearch benAdvanceSearchOBJ) { - MBeneficiarymapping benMapOBJ = new MBeneficiarymapping(); - - // for createdBy & createdDate - if (benAdvanceSearchOBJ != null) { - MBeneficiarydetail benDetailsOBJ = detailRepo - .getWith_vanSerialNo_vanID(benAdvanceSearchOBJ.getBenDetailsID(), benAdvanceSearchOBJ.getVanID()); - - if (benAdvanceSearchOBJ.getHouseHoldID() != null) - benMapOBJ.setHouseHoldID(benAdvanceSearchOBJ.getHouseHoldID()); - if (benAdvanceSearchOBJ.getGuideLineID() != null) - benMapOBJ.setGuideLineID(benAdvanceSearchOBJ.getGuideLineID()); - if (benAdvanceSearchOBJ.getRchID() != null) - benMapOBJ.setRchID(benAdvanceSearchOBJ.getRchID()); - - benMapOBJ.setBenMapId(benAdvanceSearchOBJ.getBenMapID()); - benMapOBJ.setCreatedBy(benDetailsOBJ.getCreatedBy()); - benMapOBJ.setCreatedDate(benDetailsOBJ.getCreatedDate()); - - benMapOBJ.setMBeneficiaryaddress(addressRepo.getWithVanSerialNoVanID(benAdvanceSearchOBJ.getBenAddressID(), - benAdvanceSearchOBJ.getVanID())); - benMapOBJ.setMBeneficiaryconsent(consentRepo.getWithVanSerialNoVanID(benAdvanceSearchOBJ.getBenConsentID(), - benAdvanceSearchOBJ.getVanID())); - benMapOBJ.setMBeneficiarycontact(contactRepo.getWithVanSerialNoVanID(benAdvanceSearchOBJ.getBenContactID(), - benAdvanceSearchOBJ.getVanID())); - benMapOBJ.setMBeneficiarydetail(benDetailsOBJ); - benMapOBJ.setMBeneficiaryregidmapping(regIdRepo.getWithVanSerialNoVanID(benAdvanceSearchOBJ.getBenRegID(), - benAdvanceSearchOBJ.getVanID())); - benMapOBJ.setMBeneficiaryImage(imageRepo.getWithVanSerialNoVanID(benAdvanceSearchOBJ.getBenImageID(), - benAdvanceSearchOBJ.getVanID())); - benMapOBJ.setMBeneficiaryAccount(accountRepo.getWithVanSerialNoVanID(benAdvanceSearchOBJ.getBenAccountID(), - benAdvanceSearchOBJ.getVanID())); - - benMapOBJ.setMBeneficiaryfamilymappings( - familyMapRepo.findByBenMapIdOrderByBenFamilyMapIdAsc(benAdvanceSearchOBJ.getVanSerialNo())); - benMapOBJ.setMBeneficiaryidentities(identityRepo.findByBenMapId(benAdvanceSearchOBJ.getVanSerialNo())); - } - - return benMapOBJ; - } - - private BigInteger getBigIntegerValueFromObject(Object value) { - BigInteger ret = null; - if (value != null) { - if (value instanceof BigInteger) { - ret = (BigInteger) value; - } else if (value instanceof String) { - ret = new BigInteger((String) value); - } else if (value instanceof BigDecimal) { - ret = ((BigDecimal) value).toBigInteger(); - } else if (value instanceof Number) { - ret = BigInteger.valueOf(((Number) value).longValue()); - } else { - throw new ClassCastException("Not possible to coerce [" + value + "] from class " + value.getClass() - + " into a BigInteger."); - } - } else - throw new ClassCastException("given value is null"); - return ret; - } - - /** - * The following parameters can be changed/edited once created: - First Name - - * Middle Name - Last Name - DOB/Age - Address (Current, Permanent, Emergency) - - * Contact Numbers/Email Ids - Spouse Name - Preferred Num - Preferred SMS Num - - * Email Id - Identity - * - * Following changes need Additional Authorization - First Name - Middle Name - - * Last Name - Father Name - Spouse Name - Identity - * - * @param identity - * @return - * @throws MissingMandatoryFieldsException - */ - public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFieldsException { - logger.info("IdentityService.editIdentity - start"); - if (identity.getBeneficiaryRegId() == null && null == identity.getBeneficaryId()) { - throw new MissingMandatoryFieldsException("Either of BeneficiaryID or Beneficiary Reg Id is mandatory."); - } - - MBeneficiarymapping benMapping = mappingRepo.findByBenRegIdOrderByBenMapIdAsc(identity.getBeneficiaryRegId()); - - // change in self details is implement here and other details here - logger.debug("identity.getChangeInSelfDetails = " + identity.getChangeInSelfDetails()); - logger.debug("identity.getChangeInOtherDetails = " + identity.getChangeInOtherDetails()); - logger.debug("identity.getChangeInAssociations = " + identity.getChangeInAssociations()); - if (Boolean.TRUE.equals(identity.getChangeInSelfDetails()) - || Boolean.TRUE.equals(identity.getChangeInOtherDetails()) - || Boolean.TRUE.equals(identity.getChangeInAssociations())) { - - // MBeneficiarydetail mbDetl = - // editMapper.identityEditDTOToMBeneficiarydetail(identity); - // MBeneficiarydetail mbDetl = - // convertIdentityEditDTOToMBeneficiarydetail(identity); - /** - * new logic for data sync, 26-09-2018 - */ - // getting correct beneficiaryDetailsId by passing vanSerialNo & vanID - MBeneficiarydetail benDetails = detailRepo.findBenDetailsByVanSerialNoAndVanID( - benMapping.getMBeneficiarydetail().getBeneficiaryDetailsId(), benMapping.getVanID()); - // next statement is new one, setting correct beneficiaryDetailsId - if (benDetails != null) { - // Create a new instance of MBeneficiarydetail or use the existing one - MBeneficiarydetail mbDetl = convertIdentityEditDTOToMBeneficiarydetail(identity); - - // Set fields from the existing benDetails to mbDetl - mbDetl.setBeneficiaryDetailsId(benDetails.getBeneficiaryDetailsId()); - if (benDetails.getFamilyId() != null) - mbDetl.setFamilyId(benDetails.getFamilyId()); - if (benDetails.getHeadOfFamily_RelationID() != null) - mbDetl.setHeadOfFamily_RelationID(benDetails.getHeadOfFamily_RelationID()); - if (benDetails.getHeadOfFamily_Relation() != null) - mbDetl.setHeadOfFamily_Relation(benDetails.getHeadOfFamily_Relation()); - if (benDetails.getOther() != null) - mbDetl.setOther(benDetails.getOther()); - - // Extract and set extra fields + + private static final Logger logger = LoggerFactory.getLogger(IdentityService.class); + public static final String CREATED_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; + @Autowired + private DataSource dataSource; + + private JdbcTemplate jdbcTemplate; + + private JdbcTemplate getJdbcTemplate() { + return new JdbcTemplate(dataSource); + + } + + @Autowired + private RMNCHBeneficiaryDetailsRmnchRepo rMNCHBeneficiaryDetailsRmnchRepo; + + @Autowired + private ElasticsearchService elasticsearchService; + + @Autowired + private BeneficiaryElasticsearchIndexUpdater syncService; + + @Autowired + IdentityMapper mapper; + @Autowired + IdentityEditMapper editMapper; + @Autowired + IdentitySearchMapper searchMapper; + @Autowired + private IdentityPartialMapper partialMapper; + @Autowired + BenAddressRepo addressRepo; + @Autowired + BenConsentRepo consentRepo; + @Autowired + BenContactRepo contactRepo; + @Autowired + BenDataAccessRepo accessRepo; + @Autowired + BenDetailRepo detailRepo; + @Autowired + BenFamilyMappingRepo familyMapRepo; + @Autowired + BenIdentityRepo identityRepo; + @Autowired + BenMappingRepo mappingRepo; + @Autowired + BenRegIdMappingRepo regIdRepo; + @Autowired + BenServiceMappingRepo serviceMapRepo; + @Autowired + MBeneficiaryAccountRepo accountRepo; + @Autowired + MBeneficiaryImageRepo imageRepo; + @Autowired + private BenIdImportMapper benIdImportMapper; + @Autowired + private V_BenAdvanceSearchRepo v_BenAdvanceSearchRepo; + + @Value("${elasticsearch.enabled}") + private boolean esEnabled; + + public void getBenAdress() { + logger.debug("Address count: " + addressRepo.count()); + logger.debug( + "Address By Address: " + addressRepo.findByBenAddressIDOrderByBenAddressIDAsc(new BigInteger("12"))); + logger.debug("Consent count: " + consentRepo.count()); + logger.debug("Consent By Id: " + consentRepo.findByBenConsentIDOrderByBenConsentIDAsc(new BigInteger("12"))); + logger.debug("Contact count: " + contactRepo.count()); + logger.debug("Contact By Id: " + contactRepo.findByBenContactsIDOrderByBenContactsIDAsc(new BigInteger("12"))); + logger.debug("Data Access count: " + accessRepo.count()); + logger.debug("Data Access By Id: " + accessRepo.findByAccessIdOrderByAccessIdAsc(new BigInteger("12"))); + logger.debug("Detail count: " + detailRepo.count()); + logger.debug("Detail By Address: " + + detailRepo.findByBeneficiaryDetailsIdOrderByBeneficiaryDetailsIdAsc(new BigInteger("12"))); + logger.debug("FamilyMap count: " + familyMapRepo.count()); + logger.debug( + "FamilyMap By Id: " + familyMapRepo.findByBenFamilyMapIdOrderByBenFamilyMapIdAsc(new BigInteger("12"))); + logger.debug("Identity count: " + identityRepo.count()); + logger.debug("Identity By Id: " + identityRepo.findByBenIdentityId(new BigInteger("12"))); + logger.debug("Mapping count: " + mappingRepo.count()); + logger.debug("Mapping By Id: " + mappingRepo.findByBenMapIdOrderByBenMapIdAsc(new BigInteger("12"))); + } + + /** + * + * @param searchDTO + * @return + */ + public List getBeneficiaries(IdentitySearchDTO searchDTO) + throws NoResultException, QueryTimeoutException, Exception { + List list = new ArrayList(); + + /** + * if beneficiary Id present + */ + if (searchDTO.getBeneficiaryId() != null) { + logger.info("getting beneficiaries by ID for " + searchDTO.getBeneficiaryId()); + return this.getBeneficiariesByBenId(searchDTO.getBeneficiaryId()); + } + + /** + * if beneficiary Reg Id present + */ + if (searchDTO.getBeneficiaryRegId() != null) { + logger.info("getting beneficiaries by reg ID for " + searchDTO.getBeneficiaryRegId()); + return this.getBeneficiariesByBenRegId(searchDTO.getBeneficiaryRegId()); + } + + /** + * if beneficiary Reg Id present + */ + if (searchDTO.getContactNumber() != null) { + logger.info("getting beneficiaries by contact no for " + searchDTO.getContactNumber()); + List list3 = this.getBeneficiariesByPhoneNum(searchDTO.getContactNumber()); + if (!list3.isEmpty() && searchDTO.getIsD2D() != null && Boolean.TRUE.equals(searchDTO.getIsD2D())) { + + for (int i = 0; i < list3.size(); i++) { + if (searchDTO.getFirstName() != null) { + if (list3.get(i) == null || list3.get(i).getBeneficiaryDetails() == null + || list3.get(i).getBeneficiaryDetails().getFirstName() == null + || !list3.get(i).getBeneficiaryDetails().getFirstName() + .equalsIgnoreCase(searchDTO.getFirstName())) { + list3.remove(i); + i--; + + continue; + } + + } + if (searchDTO.getLastName() != null) { + if (list3.get(i) == null || list3.get(i).getBeneficiaryDetails() == null + || list3.get(i).getBeneficiaryDetails().getLastName() == null + || !list3.get(i).getBeneficiaryDetails().getLastName() + .equalsIgnoreCase(searchDTO.getLastName())) { + list3.remove(i); + i--; + + continue; + } + } + + if (searchDTO.getGenderId() != null) { + if (list3.get(i) == null || list3.get(i).getBeneficiaryDetails() == null + || list3.get(i).getBeneficiaryDetails().getGenderId() == null || !list3.get(i) + .getBeneficiaryDetails().getGenderId().equals(searchDTO.getGenderId())) { + + list3.remove(i); + i--; + continue; + } + } + if (searchDTO.getHouseHoldID() != null) { + if (list3.get(i) == null || list3.get(i).getBeneficiaryDetails() == null + || list3.get(i).getBeneficiaryDetails().getHouseHoldID() == null || !list3.get(i) + .getBeneficiaryDetails().getHouseHoldID().equals(searchDTO.getHouseHoldID())) { + + list3.remove(i); + i--; + continue; + } + } + + if (searchDTO.getCurrentAddress().getStateId() != null) { + if (list3.get(i) == null || list3.get(i).getCurrentAddress() == null + || list3.get(i).getCurrentAddress().getStateId() == null + || !list3.get(i).getCurrentAddress().getStateId() + .equals(searchDTO.getCurrentAddress().getStateId())) { + + list3.remove(i); + i--; + continue; + } + } + + if (searchDTO.getCurrentAddress().getDistrictId() != null) { + if (list3.get(i) == null || list3.get(i).getCurrentAddress() == null + || list3.get(i).getCurrentAddress().getDistrictId() == null + || !list3.get(i).getCurrentAddress().getDistrictId() + .equals(searchDTO.getCurrentAddress().getDistrictId())) { + + list3.remove(i); + i--; + continue; + } + } + + if (searchDTO.getCurrentAddress().getVillageId() != null) { + if (list3.get(i) == null || list3.get(i).getCurrentAddress() == null + || list3.get(i).getCurrentAddress().getVillageId() == null + || !list3.get(i).getCurrentAddress().getVillageId() + .equals(searchDTO.getCurrentAddress().getVillageId())) { + + list3.remove(i); + i--; + continue; + } + } + + } + } + return list3; + } + + /** + * New logic for advance search, 03-10-2018 + */ + List tmpList = mappingRepo.dynamicFilterSearchNew(searchDTO); + for (VBenAdvanceSearch obj : tmpList) { + list.add(this.getBeneficiariesDTO(this.getBeneficiariesDTONew1(obj))); + logger.debug("benMapId: " + obj.getBenMapID()); + } + /** + * End + */ + + return list; + } + + public List getBeneficiarieswithES(IdentitySearchDTO searchDTO) + throws NoResultException, QueryTimeoutException, Exception { + List list = new ArrayList(); + // if beneficiary Id present + if (searchDTO.getBeneficiaryId() != null) { + logger.info("getting beneficiaries by ID for " + searchDTO.getBeneficiaryId()); + return this.getBeneficiariesByBenId(searchDTO.getBeneficiaryId()); + } + + // if beneficiary Reg Id present + if (searchDTO.getBeneficiaryRegId() != null) { + logger.info("getting beneficiaries by reg ID for " + searchDTO.getBeneficiaryRegId()); + return this.getBeneficiariesByBenRegId(searchDTO.getBeneficiaryRegId()); + } + + // if beneficiary Reg Id present + if (searchDTO.getContactNumber() != null) { + logger.info("getting beneficiaries by contact no for " + searchDTO.getContactNumber()); + List list3 = this.getBeneficiariesByPhoneNum(searchDTO.getContactNumber()); + if (!list3.isEmpty() && searchDTO.getIsD2D() != null && Boolean.TRUE.equals(searchDTO.getIsD2D())) { + list3 = applyD2DFilters(list3, searchDTO); + } + return list3; + } + + // Advanced search from database + List tmpList = mappingRepo.dynamicFilterSearchNew(searchDTO); + for (VBenAdvanceSearch obj : tmpList) { + list.add(this.getBeneficiariesDTO(this.getBeneficiariesDTONew1(obj))); + logger.debug("benMapId: " + obj.getBenMapID()); + } + + return list; + } + + /** + * Check if search has multiple criteria (for advanced search) + */ + private boolean hasMultipleCriteria(IdentitySearchDTO searchDTO) { + int criteriaCount = 0; + + if (searchDTO.getFirstName() != null && !searchDTO.getFirstName().trim().isEmpty()) { + criteriaCount++; + } + if (searchDTO.getLastName() != null && !searchDTO.getLastName().trim().isEmpty()) { + criteriaCount++; + } + if (searchDTO.getContactNumber() != null && !searchDTO.getContactNumber().trim().isEmpty()) { + criteriaCount++; + } + + return criteriaCount >= 2; + } + + /** + * Enrich Elasticsearch results with complete data from database ES returns + * only basic fields, fetch complete data from DB + */ + private List enrichBeneficiariesFromDatabase(List esResults) { + List enrichedList = new ArrayList<>(); + + for (BeneficiariesDTO esResult : esResults) { + try { + if (esResult.getBenRegId() != null) { + // Fetch complete data from database + List fullData = this.getBeneficiariesByBenRegId(esResult.getBenRegId()); + if (!fullData.isEmpty()) { + enrichedList.add(fullData.get(0)); + } else { + // If not in DB anymore, return ES data + enrichedList.add(esResult); + } + } + } catch (Exception e) { + logger.error("Error enriching beneficiary data: {}", e.getMessage()); + // Return ES data if enrichment fails + enrichedList.add(esResult); + } + } + + return enrichedList; + } + + /** + * Apply D2D filters to search results + */ + private List applyD2DFilters(List list, IdentitySearchDTO searchDTO) { + List filtered = new ArrayList<>(); + + for (BeneficiariesDTO dto : list) { + boolean matches = true; + + if (searchDTO.getFirstName() != null) { + if (dto.getBeneficiaryDetails() == null + || dto.getBeneficiaryDetails().getFirstName() == null + || !dto.getBeneficiaryDetails().getFirstName().equalsIgnoreCase(searchDTO.getFirstName())) { + matches = false; + } + } + + if (matches && searchDTO.getLastName() != null) { + if (dto.getBeneficiaryDetails() == null + || dto.getBeneficiaryDetails().getLastName() == null + || !dto.getBeneficiaryDetails().getLastName().equalsIgnoreCase(searchDTO.getLastName())) { + matches = false; + } + } + + if (matches && searchDTO.getGenderId() != null) { + if (dto.getBeneficiaryDetails() == null + || dto.getBeneficiaryDetails().getGenderId() == null + || !dto.getBeneficiaryDetails().getGenderId().equals(searchDTO.getGenderId())) { + matches = false; + } + } + + if (matches && searchDTO.getHouseHoldID() != null) { + if (dto.getBeneficiaryDetails() == null + || dto.getBeneficiaryDetails().getHouseHoldID() == null + || !dto.getBeneficiaryDetails().getHouseHoldID().equals(searchDTO.getHouseHoldID())) { + matches = false; + } + } + + if (matches && searchDTO.getCurrentAddress() != null && searchDTO.getCurrentAddress().getStateId() != null) { + if (dto.getCurrentAddress() == null + || dto.getCurrentAddress().getStateId() == null + || !dto.getCurrentAddress().getStateId().equals(searchDTO.getCurrentAddress().getStateId())) { + matches = false; + } + } + + if (matches && searchDTO.getCurrentAddress() != null && searchDTO.getCurrentAddress().getDistrictId() != null) { + if (dto.getCurrentAddress() == null + || dto.getCurrentAddress().getDistrictId() == null + || !dto.getCurrentAddress().getDistrictId().equals(searchDTO.getCurrentAddress().getDistrictId())) { + matches = false; + } + } + + if (matches && searchDTO.getCurrentAddress() != null && searchDTO.getCurrentAddress().getVillageId() != null) { + if (dto.getCurrentAddress() == null + || dto.getCurrentAddress().getVillageId() == null + || !dto.getCurrentAddress().getVillageId().equals(searchDTO.getCurrentAddress().getVillageId())) { + matches = false; + } + } + + if (matches) { + filtered.add(dto); + } + } + + return filtered; + } + + /** + * + * Check which parameters available Get BenMapID based on the parameter/set + * of parameters Use BenMapID to retrieve all data for a Beneficiary + * + * @param benId + * @return + */ + public List getBeneficiariesByBenId(BigInteger benId) + throws NoResultException, QueryTimeoutException, Exception { + logger.info("IdentityService.getBeneficiariesByBenId - start, beneficiaryID : " + benId); + List list = new ArrayList(); + + MBeneficiaryregidmapping regId = regIdRepo.findByBeneficiaryID(benId); + if (regId != null && regId.getBenRegId() != null) { + List benMapObjArr = mappingRepo.getBenMappingByRegID(regId.getBenRegId()); + + // new logic, 27-08-2018 + if (benMapObjArr != null && benMapObjArr.size() > 0) { + MBeneficiarymapping benMap = this.getBeneficiariesDTONew(benMapObjArr.get(0)); + list.add(this.getBeneficiariesDTO(benMap)); + } + } + logger.info("benMap size " + (list.size() == 0 ? "No Beneficiary Found" : list.size())); + // end new logic + + logger.info("IdentityService.getBeneficiariesByBenId - end - beneficiaryID : " + benId); + + return list; + } + + /** + * + * @param BenRegId + * @return + */ + public List getBeneficiariesByBenRegId(BigInteger benRegId) + throws NoResultException, QueryTimeoutException, Exception { + List list = new ArrayList(); + logger.info("IdentityService.getBeneficiariesByBenRegId - start for benRegId " + benRegId); + try { + // new logic, 27-09-2018 + List benMapObjArr = mappingRepo.getBenMappingByRegID(benRegId); + + // new logic, 27-08-2018 + if (benMapObjArr != null && !benMapObjArr.isEmpty()) { + MBeneficiarymapping benMap = this.getBeneficiariesDTONew(benMapObjArr.get(0)); + list.add(this.getBeneficiariesDTO(benMap)); + } + logger.info("benMap size" + (list.isEmpty() ? "No Beneficiary Found" : list.size())); + // end new logic + + logger.info("IdentityService.getBeneficiariesByBenRegId - end for benRegId " + benRegId); + } catch (Exception e) { + logger.error("error in beneficiary search for beneficiary reg id : " + benRegId + " error : " + + e.getLocalizedMessage()); + } + + return list; + } + + /** + * + * Check which parameters available Get BenMapID based on the parameter/set + * of parameters Use BenMapID to retrieve all data for a Beneficiary + * + * @param phoneNum + * @return + */ + public List getBeneficiariesByPhoneNum(String phoneNum) + throws NoResultException, QueryTimeoutException { + // new logic, 27-09-2018 + List list = new ArrayList<>(); + + try { + List benContact = contactRepo.findByAnyPhoneNum(phoneNum); + + logger.info(benContact.size() + " contacts found for phone number " + phoneNum); + + List benMapObjArr = new ArrayList<>(); + + for (MBeneficiarycontact benContactOBJ : benContact) { + benMapObjArr.addAll(mappingRepo.getBenMappingByBenContactIdListNew(benContactOBJ.getVanSerialNo(), + benContactOBJ.getVanID())); + } + + for (Object[] benMapOBJ : benMapObjArr) { + list.add(this.getBeneficiariesDTO(this.getBeneficiariesDTONew(benMapOBJ))); + } + + } catch (Exception e) { + logger.error( + "error in beneficiary search for phone no : " + phoneNum + " error : " + e.getLocalizedMessage()); + } + + logger.info("IdentityService.getBeneficiariesByPhoneNum - end"); + // end + return list; + } + + +/** + * Advanced search using Elasticsearch with fallback to database + */ +public Map advancedSearchBeneficiariesES( + String firstName, String middleName, String lastName, Integer genderId, java.util.Date dob, + Integer stateId, Integer districtId, Integer blockId, Integer villageId, + String fatherName, String spouseName, String maritalStatus, String phoneNumber, + String beneficiaryId, String healthId, String aadharNo, + Integer userId, String auth, Boolean is1097) throws Exception { + + try { + logger.info("IdentityService.advancedSearchBeneficiariesES - start"); + logger.info("ES enabled: {}", esEnabled); + + Map response = new HashMap<>(); + + if (esEnabled) { + logger.info("Using Elasticsearch for advanced search"); + + // Call Elasticsearch service + List> esResults = elasticsearchService.advancedSearch( + firstName, middleName, lastName, genderId, dob, stateId, districtId, + blockId, villageId, fatherName, spouseName, maritalStatus, phoneNumber, + beneficiaryId, healthId, aadharNo, userId + ); + + response.put("data", esResults); + response.put("count", esResults.size()); + response.put("source", "elasticsearch"); + + logger.info("ES returned {} results", esResults.size()); + + } else { + logger.info("ES disabled - using database for advanced search"); + + IdentitySearchDTO searchDTO = new IdentitySearchDTO(); + searchDTO.setFirstName(firstName); + searchDTO.setLastName(lastName); + searchDTO.setGenderId(genderId); + searchDTO.setDob(dob != null ? new Timestamp(dob.getTime()) : null); + searchDTO.setFatherName(fatherName); + searchDTO.setSpouseName(spouseName); + searchDTO.setContactNumber(phoneNumber); + + if (beneficiaryId != null && !beneficiaryId.trim().isEmpty()) { + try { + searchDTO.setBeneficiaryId(new BigInteger(beneficiaryId)); + } catch (NumberFormatException e) { + logger.warn("Invalid beneficiaryId format: {}", beneficiaryId); + } + } + + if (stateId != null || districtId != null || blockId != null || villageId != null) { + Address addressDTO = new Address(); + addressDTO.setStateId(stateId); + addressDTO.setDistrictId(districtId); + addressDTO.setSubDistrictId(blockId); + addressDTO.setVillageId(villageId); + searchDTO.setCurrentAddress(addressDTO); + } + + List dbResults = this.getBeneficiaries(searchDTO); + + List> formattedResults = dbResults.stream() + .map(this::convertBeneficiaryDTOToMap) + .collect(Collectors.toList()); + + response.put("data", formattedResults); + response.put("count", formattedResults.size()); + response.put("source", "database"); + + logger.info("Database returned {} results", formattedResults.size()); + } + + logger.info("IdentityService.advancedSearchBeneficiariesES - end"); + return response; + + } catch (Exception e) { + logger.error("Advanced search failed: {}", e.getMessage(), e); + throw new Exception("Error in advanced search: " + e.getMessage(), e); + } +}/** + * Convert BeneficiariesDTO to Map format + */ +private Map convertBeneficiaryDTOToMap(BeneficiariesDTO dto) { + try { + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(dto); + return mapper.readValue(json, Map.class); + } catch (Exception e) { + logger.error("Error converting DTO to map", e); + return new HashMap<>(); + } +} + + + + /** + * * + * + * Search beneficiary by healthID / ABHA address + * + * @param healthID + * @return + * @throws NoResultException + * @throws QueryTimeoutException + * @throws Exception + */ + public List getBeneficiaryByHealthIDAbhaAddress(String healthID) + throws NoResultException, QueryTimeoutException, Exception { + List beneficiaryList = new ArrayList<>(); + try { + List regIDList = v_BenAdvanceSearchRepo.getBenRegIDByHealthIDAbhaAddress(healthID); + if (regIDList != null && !regIDList.isEmpty()) { + for (BigInteger benRegID : regIDList) { + if (benRegID != null) { + List searchList = this.getBeneficiariesByBenRegId(benRegID); + beneficiaryList.addAll(searchList); + } + } + } + } catch (Exception e) { + logger.error("error in beneficiary search for health ID / ABHA Address : " + healthID + " error : " + + e.getLocalizedMessage()); + } + return beneficiaryList; + } + + /** + * * + * + * Search beneficiary by healthIDNo / ABHA ID No + * + * @param healthIDNo + * @return + * @throws NoResultException + * @throws QueryTimeoutException + * @throws Exception + */ + public List getBeneficiaryByHealthIDNoAbhaIdNo(String healthIDNo) + throws NoResultException, QueryTimeoutException, Exception { + List beneficiaryList = new ArrayList<>(); + try { + List regIDList = v_BenAdvanceSearchRepo.getBenRegIDByHealthIDNoAbhaIdNo(healthIDNo); + if (regIDList != null && !regIDList.isEmpty()) { + for (BigInteger benRegID : regIDList) { + if (benRegID != null) { + List searchList = this.getBeneficiariesByBenRegId(benRegID); + beneficiaryList.addAll(searchList); + } + } + } + } catch (Exception e) { + logger.error("error in beneficiary search for health ID No / ABHA ID No : " + healthIDNo + " error : " + + e.getLocalizedMessage()); + } + return beneficiaryList; + } + + public List searhBeneficiaryByFamilyId(String familyId) + throws NoResultException, QueryTimeoutException { + List beneficiaryList = new ArrayList<>(); + try { + + // find benmap ids + List benMapObjArr = null; + List benDetailsVanSerialNoList = new ArrayList<>(); + int vanID = 0; + + List benDetailsList = detailRepo.searchByFamilyId(familyId); + + if (benDetailsList == null || benDetailsList.isEmpty()) { + return beneficiaryList; + }else { + // considering as of now family creation is possible through facility modules + // only + vanID = benDetailsList.get(0).getVanID(); + + for (MBeneficiarydetail benDetails : benDetailsList) { + benDetailsVanSerialNoList.add(benDetails.getVanSerialNo()); + } + + benMapObjArr = mappingRepo.getBenMappingByBenDetailsIds(benDetailsVanSerialNoList, vanID); + + for (Object[] benMapOBJ : benMapObjArr) { + beneficiaryList.add(this.getBeneficiariesDTO(this.getBeneficiariesDTONew(benMapOBJ))); + } + + } + + } catch (Exception e) { + logger.error( + "error in beneficiary search for familyId : " + familyId + " error : " + e.getLocalizedMessage()); + } + return beneficiaryList; + } + + public List searchBeneficiaryByVillageIdAndLastModifyDate(List villageIDs, + Timestamp lastModifiedDate) { + + List beneficiaryList = new ArrayList<>(); + try { + // find benmap ids + List benMappingsList = mappingRepo + .findByBeneficiaryDetailsByVillageIDAndLastModifyDate(villageIDs, lastModifiedDate); + if (benMappingsList != null && !benMappingsList.isEmpty()) { + for (MBeneficiarymapping benMapOBJ : benMappingsList) { + beneficiaryList.add(this.getBeneficiariesDTO(benMapOBJ)); + } + } + + } catch (Exception e) { + logger.error("error in beneficiary search to sync to CHO App with villageIDs: {} ", + villageIDs + " error : " + e.getLocalizedMessage()); + } + return beneficiaryList; + } + + public Long countBeneficiaryByVillageIdAndLastModifyDate(List villageIDs, Timestamp lastModifiedDate) { + Long beneficiaryCount = 0L; + try { + beneficiaryCount = mappingRepo.getBeneficiaryCountsByVillageIDAndLastModifyDate(villageIDs, + lastModifiedDate); + } catch (Exception e) { + logger.error("error in getting beneficiary count to sync to CHO App with villageIDs: {},error :{} ", + villageIDs, e.getLocalizedMessage()); + } + return beneficiaryCount; + } + + public List searhBeneficiaryByGovIdentity(String identity) + throws NoResultException, QueryTimeoutException { + List beneficiaryList = new ArrayList<>(); + try { + + List benMapObjArr = new ArrayList<>(); + + // find identity no + List benIdentityList = identityRepo.searchByIdentityNo(identity); + + // find benmap ids + if (benIdentityList == null || benIdentityList.isEmpty()) { + return beneficiaryList; + }else { + for (MBeneficiaryidentity identityObj : benIdentityList) { + benMapObjArr.addAll( + mappingRepo.getBenMappingByVanSerialNo(identityObj.getBenMapId(), identityObj.getVanID())); + } + + for (Object[] benMapOBJ : benMapObjArr) { + beneficiaryList.add(this.getBeneficiariesDTO(this.getBeneficiariesDTONew(benMapOBJ))); + } + + } + + } catch (Exception e) { + logger.error("error in beneficiary search for gov identity : " + identity + " error : " + + e.getLocalizedMessage()); + } + return beneficiaryList; + } + + private MBeneficiarymapping getBeneficiariesDTONew(Object[] benMapArr) { + MBeneficiarymapping mapping = new MBeneficiarymapping(); + if (benMapArr != null && benMapArr.length == 12 && benMapArr[8] != null && benMapArr[9] != null) { + mapping.setBenMapId(getBigIntegerValueFromObject(benMapArr[0])); + mapping.setCreatedBy(String.valueOf(benMapArr[10])); + mapping.setCreatedDate((Timestamp) benMapArr[11]); + mapping = mappingRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[9]), (Integer) benMapArr[8]); + MBeneficiaryaddress address = addressRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[1]), (Integer) benMapArr[8]); + MBeneficiaryconsent consent = consentRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[2]), (Integer) benMapArr[8]); + MBeneficiarycontact contact = contactRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[3]), (Integer) benMapArr[8]); + MBeneficiarydetail details = detailRepo.getWith_vanSerialNo_vanID(getBigIntegerValueFromObject(benMapArr[4]), (Integer) benMapArr[8]); + MBeneficiaryregidmapping regidmapping = regIdRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[5]), (Integer) benMapArr[8]); + MBeneficiaryAccount account = accountRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[7]), (Integer) benMapArr[8]); + MBeneficiaryImage image = imageRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[6]), (Integer) benMapArr[8]); + List servicemap = serviceMapRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[0]), (Integer) benMapArr[8]); + List identity = identityRepo.findByBenMapIdAndVanID(getBigIntegerValueFromObject(benMapArr[0]), (Integer) benMapArr[8]); + List familymapping = familyMapRepo.findByBenMapIdAndVanIDOrderByBenFamilyMapIdAsc(getBigIntegerValueFromObject(benMapArr[0]), (Integer) benMapArr[8]); + + mapping.setMBeneficiaryaddress(address); + mapping.setMBeneficiaryconsent(consent); + mapping.setMBeneficiarycontact(contact); + mapping.setMBeneficiarydetail(details); + mapping.setMBeneficiaryfamilymappings(familymapping); + mapping.setMBeneficiaryidentities(identity); + mapping.setMBeneficiaryImage(image); + mapping.setMBeneficiaryregidmapping(regidmapping); + mapping.setMBeneficiaryservicemappings(servicemap); + mapping.setMBeneficiaryAccount(account); + //benMapOBJ = mappingRepo.getMapping(getBigIntegerValueFromObject(benMapArr[9]), (Integer) benMapArr[8]); + + BigInteger benRegId = new BigInteger(benMapArr[5].toString()); + RMNCHBeneficiaryDetailsRmnch obj = rMNCHBeneficiaryDetailsRmnchRepo + .getByRegID(benRegId); + + if (obj != null) { + if (obj.getHouseoldId() != null) { + mapping.setHouseHoldID(obj.getHouseoldId()); + } + if (obj.getGuidelineId() != null) { + mapping.setGuideLineID(obj.getGuidelineId()); + } + if (obj.getRchid() != null) { + mapping.setRchID(obj.getRchid()); + } + } + + } + return mapping; + } + + private MBeneficiarymapping getBeneficiariesDTONewPartial(Object[] benMapArr) { + MBeneficiarymapping benMapOBJ = new MBeneficiarymapping(); + + benMapOBJ.setBenMapId(getBigIntegerValueFromObject(benMapArr[0])); + benMapOBJ.setCreatedBy(String.valueOf(benMapArr[10])); + benMapOBJ.setCreatedDate((Timestamp) benMapArr[11]); + + if (benMapArr.length == 12 && benMapArr[8] != null && benMapArr[9] != null) { + + benMapOBJ.setMBeneficiarydetail(detailRepo + .getWith_vanSerialNo_vanID(getBigIntegerValueFromObject(benMapArr[4]), (Integer) benMapArr[8])); + benMapOBJ.setMBeneficiaryregidmapping(regIdRepo + .getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[5]), (Integer) benMapArr[8])); + } + return benMapOBJ; + } + + // 03-10-2018 + // get ben mapping object from v_benadvancesearch + private MBeneficiarymapping getBeneficiariesDTONew1(VBenAdvanceSearch benAdvanceSearchOBJ) { + MBeneficiarymapping benMapOBJ = new MBeneficiarymapping(); + + // for createdBy & createdDate + if (benAdvanceSearchOBJ != null) { + MBeneficiarydetail benDetailsOBJ = detailRepo + .getWith_vanSerialNo_vanID(benAdvanceSearchOBJ.getBenDetailsID(), benAdvanceSearchOBJ.getVanID()); + + if (benAdvanceSearchOBJ.getHouseHoldID() != null) { + benMapOBJ.setHouseHoldID(benAdvanceSearchOBJ.getHouseHoldID()); + } + if (benAdvanceSearchOBJ.getGuideLineID() != null) { + benMapOBJ.setGuideLineID(benAdvanceSearchOBJ.getGuideLineID()); + } + if (benAdvanceSearchOBJ.getRchID() != null) { + benMapOBJ.setRchID(benAdvanceSearchOBJ.getRchID()); + } + + benMapOBJ.setBenMapId(benAdvanceSearchOBJ.getBenMapID()); + benMapOBJ.setCreatedBy(benDetailsOBJ.getCreatedBy()); + benMapOBJ.setCreatedDate(benDetailsOBJ.getCreatedDate()); + + benMapOBJ.setMBeneficiaryaddress(addressRepo.getWithVanSerialNoVanID(benAdvanceSearchOBJ.getBenAddressID(), + benAdvanceSearchOBJ.getVanID())); + benMapOBJ.setMBeneficiaryconsent(consentRepo.getWithVanSerialNoVanID(benAdvanceSearchOBJ.getBenConsentID(), + benAdvanceSearchOBJ.getVanID())); + benMapOBJ.setMBeneficiarycontact(contactRepo.getWithVanSerialNoVanID(benAdvanceSearchOBJ.getBenContactID(), + benAdvanceSearchOBJ.getVanID())); + benMapOBJ.setMBeneficiarydetail(benDetailsOBJ); + benMapOBJ.setMBeneficiaryregidmapping(regIdRepo.getWithVanSerialNoVanID(benAdvanceSearchOBJ.getBenRegID(), + benAdvanceSearchOBJ.getVanID())); + benMapOBJ.setMBeneficiaryImage(imageRepo.getWithVanSerialNoVanID(benAdvanceSearchOBJ.getBenImageID(), + benAdvanceSearchOBJ.getVanID())); + benMapOBJ.setMBeneficiaryAccount(accountRepo.getWithVanSerialNoVanID(benAdvanceSearchOBJ.getBenAccountID(), + benAdvanceSearchOBJ.getVanID())); + + benMapOBJ.setMBeneficiaryfamilymappings( + familyMapRepo.findByBenMapIdOrderByBenFamilyMapIdAsc(benAdvanceSearchOBJ.getVanSerialNo())); + benMapOBJ.setMBeneficiaryidentities(identityRepo.findByBenMapId(benAdvanceSearchOBJ.getVanSerialNo())); + } + + return benMapOBJ; + } + + private BigInteger getBigIntegerValueFromObject(Object value) { + BigInteger ret = null; + if (value != null) { + if (value instanceof BigInteger) { + ret = (BigInteger) value; + } else if (value instanceof String) { + ret = new BigInteger((String) value); + } else if (value instanceof BigDecimal) { + ret = ((BigDecimal) value).toBigInteger(); + } else if (value instanceof Number) { + ret = BigInteger.valueOf(((Number) value).longValue()); + } else { + throw new ClassCastException("Not possible to coerce [" + value + "] from class " + value.getClass() + + " into a BigInteger."); + } + } else { + throw new ClassCastException("given value is null"); + } + return ret; + } + + /** + * The following parameters can be changed/edited once created: - First Name + * - Middle Name - Last Name - DOB/Age - Address (Current, Permanent, + * Emergency) - Contact Numbers/Email Ids - Spouse Name - Preferred Num - + * Preferred SMS Num - Email Id - Identity + * + * Following changes need Additional Authorization - First Name - Middle + * Name - Last Name - Father Name - Spouse Name - Identity + * + * @param identity + * @return + * @throws MissingMandatoryFieldsException + */ + public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFieldsException { + logger.info("IdentityService.editIdentity - start"); + if (identity.getBeneficiaryRegId() == null && null == identity.getBeneficaryId()) { + throw new MissingMandatoryFieldsException("Either of BeneficiaryID or Beneficiary Reg Id is mandatory."); + } + + MBeneficiarymapping benMapping = mappingRepo.findByBenRegIdOrderByBenMapIdAsc(identity.getBeneficiaryRegId()); + + // Track if ANY change happened (determines if ES sync is needed) + boolean isDataUpdate = false; + + // change in self details is implement here and other details here + logger.debug("identity.getChangeInSelfDetails = " + identity.getChangeInSelfDetails()); + logger.debug("identity.getChangeInOtherDetails = " + identity.getChangeInOtherDetails()); + logger.debug("identity.getChangeInAssociations = " + identity.getChangeInAssociations()); + if (Boolean.TRUE.equals(identity.getChangeInSelfDetails()) + || Boolean.TRUE.equals(identity.getChangeInOtherDetails()) + || Boolean.TRUE.equals(identity.getChangeInAssociations())) { + + // MBeneficiarydetail mbDetl = + // editMapper.identityEditDTOToMBeneficiarydetail(identity); + // MBeneficiarydetail mbDetl = + // convertIdentityEditDTOToMBeneficiarydetail(identity); + /** + * new logic for data sync, 26-09-2018 + */ + // getting correct beneficiaryDetailsId by passing vanSerialNo & vanID + MBeneficiarydetail benDetails = detailRepo.findBenDetailsByVanSerialNoAndVanID( + benMapping.getMBeneficiarydetail().getBeneficiaryDetailsId(), benMapping.getVanID()); + // next statement is new one, setting correct beneficiaryDetailsId + if (benDetails != null) { + // Create a new instance of MBeneficiarydetail or use the existing one + MBeneficiarydetail mbDetl = convertIdentityEditDTOToMBeneficiarydetail(identity); + + // Set fields from the existing benDetails to mbDetl + mbDetl.setBeneficiaryDetailsId(benDetails.getBeneficiaryDetailsId()); + if (benDetails.getFamilyId() != null) { + mbDetl.setFamilyId(benDetails.getFamilyId()); + } + if (benDetails.getHeadOfFamily_RelationID() != null) { + mbDetl.setHeadOfFamily_RelationID(benDetails.getHeadOfFamily_RelationID()); + } + if (benDetails.getHeadOfFamily_Relation() != null) { + mbDetl.setHeadOfFamily_Relation(benDetails.getHeadOfFamily_Relation()); + } + if (benDetails.getOther() != null) { + mbDetl.setOther(benDetails.getOther()); + } + + // Extract and set extra fields // String identityJson = new Gson().toJson(json); // JsonObject identityJsonObject = new Gson().fromJson(identityJson, JsonObject.class); // JsonObject otherFieldsJson = new JsonObject(); @@ -778,1052 +1061,1106 @@ public void editIdentity(IdentityEditDTO identity) throws MissingMandatoryFields // } // } // String otherFieldsJsonString = otherFieldsJson.toString(); - // mbDetl.setOtherFields(benDetails.getOtherFields()); - logger.debug("Beneficiary details to update = " + new OutputMapper().gson().toJson(mbDetl)); - if (benDetails.getEmergencyRegistration() != null && benDetails.getEmergencyRegistration()) { - mbDetl.setEmergencyRegistration(true); - } else { - mbDetl.setEmergencyRegistration(false); - } - detailRepo.save(mbDetl); - } - } - // edition in current emergency and permanent is implement below - logger.debug("identity.getChangeInAddress = " + identity.getChangeInAddress()); - if (Boolean.TRUE.equals(identity.getChangeInAddress())) { - - MBeneficiaryaddress mbAddr = editMapper.identityEditDTOToMBeneficiaryaddress(identity); - - /** - * new logic for data sync, 26-09-2018 - */ - - // getting correct beneficiaryDetailsId by passing vanSerialNo & vanID - BigInteger benAddressID = addressRepo.findIdByVanSerialNoAndVanID( - benMapping.getMBeneficiaryaddress().getBenAddressID(), benMapping.getVanID()); - // next statement is new one, setting correct beneficiaryDetailsId - if (benAddressID != null) - mbAddr.setBenAddressID(benAddressID); - else - throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); - - /** - * END - */ - - logger.debug("Beneficiary address to update = " + OutputMapper.gson().toJson(mbAddr)); - addressRepo.save(mbAddr); - } - - // edition in beneficiary contacts is updated here - logger.debug("identity.getChangeInContacts = " + identity.getChangeInContacts()); - if (Boolean.TRUE.equals(identity.getChangeInContacts())) { - - MBeneficiarycontact benCon = editMapper.identityEdiDTOToMBeneficiarycontact(identity); - - /** - * new logic for data sync, 26-09-2018 - */ - - // getting correct beneficiaryDetailsId by passing vanSerialNo & vanID - BigInteger benContactsID = contactRepo.findIdByVanSerialNoAndVanID( - benMapping.getMBeneficiarycontact().getBenContactsID(), benMapping.getVanID()); - // next statement is new one, setting correct beneficiaryDetailsId - if (benContactsID != null) - benCon.setBenContactsID(benContactsID); - else - throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); - - /** - * END - */ - - logger.debug("Beneficiary contact to update = " + OutputMapper.gson().toJson(benCon)); - contactRepo.save(benCon); - } - - // change in identities are added here - logger.debug("identity.getChangeInIdentities = " + identity.getChangeInIdentities()); - if (Boolean.TRUE.equals(identity.getChangeInIdentities())) { - - MBeneficiaryidentity beneficiaryidentity; - List identities = editMapper - .identityEditDTOListToMBeneficiaryidentityList(identity.getIdentities()); - logger.debug("identities to upsert = " + OutputMapper.gson().toJson(identities)); - - // new logic for getting beneficiary identities, 26-09-2018 - List idList = identityRepo.findByBenMapId(benMapping.getVanSerialNo()); - - logger.debug("existing identies = " + OutputMapper.gson().toJson(idList)); - ListIterator iterator = identities.listIterator(); - int index = 0; - while (iterator.hasNext()) { - beneficiaryidentity = iterator.next(); - - // new logic, 26-09-2018 - beneficiaryidentity.setBenMapId(benMapping.getVanSerialNo()); - logger.debug("Beneficiary identity to update = " + OutputMapper.gson().toJson(beneficiaryidentity)); - if (index < idList.size() && beneficiaryidentity.getBenIdentityId() == null) { - beneficiaryidentity.setBenIdentityId(idList.get(index).getBenIdentityId()); - } - - // new code to set vanID & parkingPlaceID for new record, 26-09-2018 - if (index >= idList.size() && beneficiaryidentity.getBenIdentityId() == null) { - beneficiaryidentity.setVanID(benMapping.getVanID()); - beneficiaryidentity.setParkingPlaceID(benMapping.getParkingPlaceID()); - } - - // new logic, 26-09-2018 - MBeneficiaryidentity m = identityRepo.save(beneficiaryidentity); - - // new code, update van serial no for new entry, 26-09-2018 - if (index >= idList.size() && beneficiaryidentity.getBenIdentityId() == null) { - identityRepo.updateVanSerialNo(m.getBenIdentityId()); - } - - index++; - } - } - - // family detail changes are performing here - logger.debug("identity.getChangeInFamilyDetails = " + identity.getChangeInFamilyDetails()); - if (Boolean.TRUE.equals(identity.getChangeInFamilyDetails())) { - List fbMaps = editMapper - .identityEditDTOListToMBeneficiaryfamilymappingList(identity.getBenFamilyDTOs()); - - logger.debug("family map to upsert = " + OutputMapper.gson().toJson(fbMaps)); - - // new logic, 26-09-2018 - List fmList = familyMapRepo - .findByBenMapIdOrderByBenFamilyMapIdAsc(benMapping.getVanSerialNo()); - - logger.debug("family map stored = " + OutputMapper.gson().toJson(fmList)); - ListIterator iterator = fbMaps.listIterator(); - MBeneficiaryfamilymapping familymapping; - int index = 0; - while (iterator.hasNext()) { - - familymapping = iterator.next(); - // new logic, 26-09-2018 - familymapping.setBenMapId(benMapping.getVanSerialNo()); - - logger.debug("family mapping to update = " + OutputMapper.gson().toJson(familymapping)); - if (index < fmList.size()) { - familymapping.setBenFamilyMapId(fmList.get(index).getBenFamilyMapId()); - } - - if (index >= fmList.size() && familymapping.getBenFamilyMapId() == null) { - familymapping.setVanID(benMapping.getVanID()); - familymapping.setParkingPlaceID(benMapping.getParkingPlaceID()); - } - - // new logic, 26-09-2018 - MBeneficiaryfamilymapping m = familyMapRepo.save(familymapping); - - // new code, update van serial no for new entry, 26-09-2018 - if (familymapping.getBenFamilyMapId() == null) { - familyMapRepo.updateVanSerialNo(m.getBenFamilyMapId()); - } - - index++; - } - } - - // start - // Feature used in outreach - if (Boolean.TRUE.equals(identity.getChangeInBankDetails())) { - MBeneficiaryAccount beneficiaryAccount = editMapper.identityEditDTOToMBeneficiaryAccount(identity); - - /** - * new logic for data sync, 26-09-2018 - */ - // getting correct beneficiaryDetailsId by passing vanSerialNo & vanID - BigInteger benAccountID = accountRepo.findIdByVanSerialNoAndVanID( - benMapping.getMBeneficiaryAccount().getBenAccountID(), benMapping.getVanID()); - // next statement is new one, setting correct beneficiaryDetailsId - if (benAccountID != null) - beneficiaryAccount.setBenAccountID(benAccountID); - else - throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); - - /** - * END - */ - - logger.debug("Account to upsert = " + OutputMapper.gson().toJson(beneficiaryAccount)); - accountRepo.save(beneficiaryAccount); - } - - if (Boolean.TRUE.equals(identity.getChangeInBenImage())) { - MBeneficiaryImage beneficiaryImage = editMapper.identityEditDTOToMBeneficiaryImage(identity); - - /** - * new logic for data sync, 26-09-2018 - */ - // getting correct beneficiaryDetailsId by passing vanSerialNo & vanID - BigInteger benImageId = imageRepo.findIdByVanSerialNoAndVanID( - benMapping.getMBeneficiaryImage().getBenImageId(), benMapping.getVanID()); - // next statement is new one, setting correct beneficiaryDetailsId - if (benImageId != null) - beneficiaryImage.setBenImageId(benImageId); - else - throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); - - /** - * END - */ - - logger.debug("Image to upsert = " + OutputMapper.gson().toJson(beneficiaryImage)); - beneficiaryImage.setProcessed("N"); - imageRepo.save(beneficiaryImage); - } - - logger.info("IdentityService.editIdentity - end. id = " + benMapping.getBenMapId()); - } - - private MBeneficiarydetail convertIdentityEditDTOToMBeneficiarydetail(IdentityEditDTO dto) { - MBeneficiarydetail beneficiarydetail = new MBeneficiarydetail(); - - // Directly set values without using @Mapping - beneficiarydetail.setAreaId(dto.getAreaId()); - beneficiarydetail.setBeneficiaryRegID(dto.getBeneficiaryRegId()); - beneficiarydetail.setCommunity(dto.getCommunity()); - beneficiarydetail.setLiteracyStatus(dto.getLiteracyStatus()); - beneficiarydetail.setCommunityId(dto.getCommunityId()); - beneficiarydetail.setDob(dto.getDob()); - beneficiarydetail.setEducation(dto.getEducation()); - beneficiarydetail.setEducationId(dto.getEducationId()); - beneficiarydetail.setHealthCareWorkerId(dto.getHealthCareWorkerId()); - beneficiarydetail.setHealthCareWorker(dto.getHealthCareWorker()); - beneficiarydetail.setFatherName(dto.getFatherName()); - beneficiarydetail.setMotherName(dto.getMotherName()); - beneficiarydetail.setFirstName(dto.getFirstName()); - beneficiarydetail.setGender(dto.getGender()); - beneficiarydetail.setGenderId(dto.getGenderId()); - beneficiarydetail.setIncomeStatusId(dto.getIncomeStatusId()); - beneficiarydetail.setIncomeStatus(dto.getIncomeStatus()); - beneficiarydetail.setMonthlyFamilyIncome(dto.getMonthlyFamilyIncome()); - if (dto.getLastName() != null) - beneficiarydetail.setLastName(dto.getLastName()); - beneficiarydetail.setMaritalStatusId(dto.getMaritalStatusId()); - beneficiarydetail.setMaritalStatus(dto.getMaritalStatus()); - beneficiarydetail.setMiddleName(dto.getMiddleName()); - beneficiarydetail.setOccupation(dto.getOccupationName()); - beneficiarydetail.setOccupationId(dto.getOccupationId()); - beneficiarydetail.setPhcId(dto.getPhcId()); - beneficiarydetail.setPlaceOfWork(dto.getPlaceOfWork()); - beneficiarydetail.setPreferredLanguage(dto.getPreferredLanguage()); - beneficiarydetail.setReligion(dto.getReligion()); - if (dto.getReligionId() != null) - beneficiarydetail.setReligionId(BigInteger.valueOf(dto.getReligionId())); - beneficiarydetail.setRemarks(dto.getRemarks()); - beneficiarydetail.setServicePointId(dto.getServicePointId()); - beneficiarydetail.setSourceOfInfo(dto.getSourceOfInfo()); - beneficiarydetail.setSpouseName(dto.getSpouseName()); - beneficiarydetail.setStatus(dto.getStatus()); - beneficiarydetail.setTitle(dto.getTitle()); - beneficiarydetail.setTitleId(dto.getTitleId()); - beneficiarydetail.setZoneId(dto.getZoneId()); - beneficiarydetail.setCreatedBy(dto.getAgentName()); - beneficiarydetail.setCreatedDate(dto.getEventTypeDate()); - beneficiarydetail.setIsHIVPositive(MBeneficiarydetail.setIsHIVPositive(dto.getIsHIVPositive())); - beneficiarydetail.setAgeAtMarriage( - MBeneficiarydetail.getAgeAtMarriageCalc(dto.getDob(), dto.getMarriageDate(), dto.getAgeAtMarriage())); - beneficiarydetail.setMarriageDate( - MBeneficiarydetail.getMarriageDateCalc(dto.getDob(), dto.getMarriageDate(), dto.getAgeAtMarriage())); - if(dto.getOtherFields() != null) - beneficiarydetail.setOtherFields(dto.getOtherFields()); - - return beneficiarydetail; - } - - /** - * @param identity - * @return - */ - - ArrayDeque queue = new ArrayDeque<>(); - - public BeneficiaryCreateResp createIdentity(IdentityDTO identity) { - logger.info("IdentityService.createIdentity - start"); - - List list = null; - MBeneficiaryregidmapping regMap = null; - synchronized (queue) { - if (queue.isEmpty()) { - logger.info("fetching 10000 rows"); - list = regIdRepo.findTop10000ByProvisionedAndReserved(false, false); - logger.info("Adding SynchronousQueue start-- "); - for (MBeneficiaryregidmapping map : list) { - queue.add(map); - } - logger.info("Adding SynchronousQueue end-- "); - } - regMap = queue.removeFirst(); - } - regMap.setReserved(true); - if (regMap.getCreatedDate() == null) { - SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); - String dateToStoreInDataBase = sdf.format(new Date()); - Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); - regMap.setCreatedDate(ts); - } - - regIdRepo.save(regMap); - - regMap.setProvisioned(true); - - logger.info("IdentityService.createIdentity - saving Address"); - ObjectMapper objectMapper = new ObjectMapper(); - MBeneficiaryaddress mAddr = identityDTOToMBeneficiaryaddress(identity); - // MBeneficiaryaddress mAddr1 = - // mapper.identityDTOToMBeneficiaryaddress(identity); - logger.info("identity.getIsPermAddrSameAsCurrAddr = " + identity.getIsPermAddrSameAsCurrAddr()); - if (Boolean.TRUE.equals(identity.getIsPermAddrSameAsCurrAddr())) { - logger.debug("identity.getCurrentAddress = " + identity.getCurrentAddress()); - mAddr.setPermanentAddress(identity.getCurrentAddress()); - } - - logger.info("identity.getIsPermAddrSameAsEmerAddr = " + identity.getIsPermAddrSameAsEmerAddr()); - if (Boolean.TRUE.equals(identity.getIsPermAddrSameAsEmerAddr())) { - logger.debug("identity.getEmergencyAddress = " + identity.getEmergencyAddress()); - mAddr.setPermanentAddress(identity.getEmergencyAddress()); - } - - logger.info("identity.getIsEmerAddrSameAsCurrAddr = " + identity.getIsEmerAddrSameAsCurrAddr()); - if (Boolean.TRUE.equals(identity.getIsEmerAddrSameAsCurrAddr())) { - logger.debug("identity.getCurrentAddress = " + identity.getCurrentAddress()); - mAddr.setEmergencyAddress(identity.getCurrentAddress()); - } - - logger.info("identity.getIsEmerAddrSameAsPermAddr = " + identity.getIsEmerAddrSameAsPermAddr()); - if (Boolean.TRUE.equals(identity.getIsEmerAddrSameAsPermAddr())) { - logger.debug("identity.getPermanentAddress = " + identity.getPermanentAddress()); - mAddr.setEmergencyAddress(identity.getPermanentAddress()); - } - if (mAddr.getCreatedDate() == null) { - SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); - String dateToStoreInDataBase = sdf.format(new Date()); - Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); - mAddr.setCreatedDate(ts); - } - - mAddr = addressRepo.save(mAddr); - logger.info("IdentityService.createIdentity - Address saved - id = " + mAddr.getBenAddressID()); - - // Update van serial no for data sync - addressRepo.updateVanSerialNo(mAddr.getBenAddressID()); - - MBeneficiaryconsent mConsnt = mapper.identityDTOToDefaultMBeneficiaryconsent(identity, true, false); - logger.info("IdentityService.createIdentity - saving Consent"); - if (mConsnt.getCreatedDate() == null) { - SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); - String dateToStoreInDataBase = sdf.format(new Date()); - Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); - mConsnt.setCreatedDate(ts); - } - mConsnt = consentRepo.save(mConsnt); - logger.info("IdentityService.createIdentity - Consent saved - id = " + mConsnt.getBenConsentID()); - - // Update van serial no for data sync - consentRepo.updateVanSerialNo(mConsnt.getBenConsentID()); - - logger.info("IdentityService.createIdentity - saving Contacts"); - MBeneficiarycontact mContc = identityDTOToMBeneficiarycontact(identity); - - if (mContc.getEmergencyContactNum() != null) { - mContc.setEmergencyContactNum(cleanPhoneNumber(mContc.getEmergencyContactNum())); - } + // mbDetl.setOtherFields(benDetails.getOtherFields()); + logger.debug("Beneficiary details to update = " + new OutputMapper().gson().toJson(mbDetl)); + if (benDetails.getEmergencyRegistration() != null && benDetails.getEmergencyRegistration()) { + mbDetl.setEmergencyRegistration(true); + } else { + mbDetl.setEmergencyRegistration(false); + } + detailRepo.save(mbDetl); + isDataUpdate = true; + } + } + // edition in current emergency and permanent is implement below + logger.debug("identity.getChangeInAddress = " + identity.getChangeInAddress()); + if (Boolean.TRUE.equals(identity.getChangeInAddress())) { + + MBeneficiaryaddress mbAddr = editMapper.identityEditDTOToMBeneficiaryaddress(identity); + + /** + * new logic for data sync, 26-09-2018 + */ + // getting correct beneficiaryDetailsId by passing vanSerialNo & vanID + BigInteger benAddressID = addressRepo.findIdByVanSerialNoAndVanID( + benMapping.getMBeneficiaryaddress().getBenAddressID(), benMapping.getVanID()); + // next statement is new one, setting correct beneficiaryDetailsId + if (benAddressID != null) { + mbAddr.setBenAddressID(benAddressID); + }else { + throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); + } + + /** + * END + */ + logger.debug("Beneficiary address to update = " + OutputMapper.gson().toJson(mbAddr)); + addressRepo.save(mbAddr); + isDataUpdate = true; + } + + // edition in beneficiary contacts is updated here + logger.debug("identity.getChangeInContacts = " + identity.getChangeInContacts()); + if (Boolean.TRUE.equals(identity.getChangeInContacts())) { + + MBeneficiarycontact benCon = editMapper.identityEdiDTOToMBeneficiarycontact(identity); + + /** + * new logic for data sync, 26-09-2018 + */ + // getting correct beneficiaryDetailsId by passing vanSerialNo & vanID + BigInteger benContactsID = contactRepo.findIdByVanSerialNoAndVanID( + benMapping.getMBeneficiarycontact().getBenContactsID(), benMapping.getVanID()); + // next statement is new one, setting correct beneficiaryDetailsId + if (benContactsID != null) { + benCon.setBenContactsID(benContactsID); + }else { + throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); + } + + /** + * END + */ + logger.debug("Beneficiary contact to update = " + OutputMapper.gson().toJson(benCon)); + contactRepo.save(benCon); + isDataUpdate = true; + + } + + // change in identities are added here + logger.debug("identity.getChangeInIdentities = " + identity.getChangeInIdentities()); + if (Boolean.TRUE.equals(identity.getChangeInIdentities())) { + + MBeneficiaryidentity beneficiaryidentity; + List identities = editMapper + .identityEditDTOListToMBeneficiaryidentityList(identity.getIdentities()); + logger.debug("identities to upsert = " + OutputMapper.gson().toJson(identities)); + + // new logic for getting beneficiary identities, 26-09-2018 + List idList = identityRepo.findByBenMapId(benMapping.getVanSerialNo()); + + logger.debug("existing identies = " + OutputMapper.gson().toJson(idList)); + ListIterator iterator = identities.listIterator(); + int index = 0; + while (iterator.hasNext()) { + beneficiaryidentity = iterator.next(); + + // new logic, 26-09-2018 + beneficiaryidentity.setBenMapId(benMapping.getVanSerialNo()); + logger.debug("Beneficiary identity to update = " + OutputMapper.gson().toJson(beneficiaryidentity)); + if (index < idList.size() && beneficiaryidentity.getBenIdentityId() == null) { + beneficiaryidentity.setBenIdentityId(idList.get(index).getBenIdentityId()); + } + + // new code to set vanID & parkingPlaceID for new record, 26-09-2018 + if (index >= idList.size() && beneficiaryidentity.getBenIdentityId() == null) { + beneficiaryidentity.setVanID(benMapping.getVanID()); + beneficiaryidentity.setParkingPlaceID(benMapping.getParkingPlaceID()); + } + + // new logic, 26-09-2018 + MBeneficiaryidentity m = identityRepo.save(beneficiaryidentity); + + // new code, update van serial no for new entry, 26-09-2018 + if (index >= idList.size() && beneficiaryidentity.getBenIdentityId() == null) { + identityRepo.updateVanSerialNo(m.getBenIdentityId()); + } + + index++; + isDataUpdate = true; + + } + } + + // family detail changes are performing here + logger.debug("identity.getChangeInFamilyDetails = " + identity.getChangeInFamilyDetails()); + if (Boolean.TRUE.equals(identity.getChangeInFamilyDetails())) { + List fbMaps = editMapper + .identityEditDTOListToMBeneficiaryfamilymappingList(identity.getBenFamilyDTOs()); + + logger.debug("family map to upsert = " + OutputMapper.gson().toJson(fbMaps)); + + // new logic, 26-09-2018 + List fmList = familyMapRepo + .findByBenMapIdOrderByBenFamilyMapIdAsc(benMapping.getVanSerialNo()); + + logger.debug("family map stored = " + OutputMapper.gson().toJson(fmList)); + ListIterator iterator = fbMaps.listIterator(); + MBeneficiaryfamilymapping familymapping; + int index = 0; + while (iterator.hasNext()) { + + familymapping = iterator.next(); + // new logic, 26-09-2018 + familymapping.setBenMapId(benMapping.getVanSerialNo()); + + logger.debug("family mapping to update = " + OutputMapper.gson().toJson(familymapping)); + if (index < fmList.size()) { + familymapping.setBenFamilyMapId(fmList.get(index).getBenFamilyMapId()); + } + + if (index >= fmList.size() && familymapping.getBenFamilyMapId() == null) { + familymapping.setVanID(benMapping.getVanID()); + familymapping.setParkingPlaceID(benMapping.getParkingPlaceID()); + } + + // new logic, 26-09-2018 + MBeneficiaryfamilymapping m = familyMapRepo.save(familymapping); + + // new code, update van serial no for new entry, 26-09-2018 + if (familymapping.getBenFamilyMapId() == null) { + familyMapRepo.updateVanSerialNo(m.getBenFamilyMapId()); + } + + index++; + isDataUpdate = true; + + } + } + + // start + // Feature used in outreach + if (Boolean.TRUE.equals(identity.getChangeInBankDetails())) { + MBeneficiaryAccount beneficiaryAccount = editMapper.identityEditDTOToMBeneficiaryAccount(identity); + + /** + * new logic for data sync, 26-09-2018 + */ + // getting correct beneficiaryDetailsId by passing vanSerialNo & vanID + BigInteger benAccountID = accountRepo.findIdByVanSerialNoAndVanID( + benMapping.getMBeneficiaryAccount().getBenAccountID(), benMapping.getVanID()); + // next statement is new one, setting correct beneficiaryDetailsId + if (benAccountID != null) { + beneficiaryAccount.setBenAccountID(benAccountID); + }else { + throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); + } + + /** + * END + */ + logger.debug("Account to upsert = " + OutputMapper.gson().toJson(beneficiaryAccount)); + accountRepo.save(beneficiaryAccount); + isDataUpdate = true; + + } + + if (Boolean.TRUE.equals(identity.getChangeInBenImage())) { + MBeneficiaryImage beneficiaryImage = editMapper.identityEditDTOToMBeneficiaryImage(identity); + + /** + * new logic for data sync, 26-09-2018 + */ + // getting correct beneficiaryDetailsId by passing vanSerialNo & vanID + BigInteger benImageId = imageRepo.findIdByVanSerialNoAndVanID( + benMapping.getMBeneficiaryImage().getBenImageId(), benMapping.getVanID()); + // next statement is new one, setting correct beneficiaryDetailsId + if (benImageId != null) { + beneficiaryImage.setBenImageId(benImageId); + }else { + throw new MissingMandatoryFieldsException("Either of vanSerialNO or vanID is missing."); + } + + /** + * END + */ + logger.debug("Image to upsert = " + OutputMapper.gson().toJson(beneficiaryImage)); + beneficiaryImage.setProcessed("N"); + imageRepo.save(beneficiaryImage); + isDataUpdate = true; + + } + + // Trigger async sync to Elasticsearch + if (isDataUpdate && identity.getBeneficiaryRegId() != null) { + logger.info("Triggering Elasticsearch sync for benRegId: {}", identity.getBeneficiaryRegId()); + syncService.syncBeneficiaryAsync(identity.getBeneficiaryRegId()); + } - if (mContc.getPhoneNum1() != null) { - mContc.setPhoneNum1(cleanPhoneNumber(mContc.getPhoneNum1())); + logger.info("IdentityService.editIdentity - end. id = " + benMapping.getBenMapId()); +} + + + + private MBeneficiarydetail convertIdentityEditDTOToMBeneficiarydetail(IdentityEditDTO dto) { + MBeneficiarydetail beneficiarydetail = new MBeneficiarydetail(); + + // Directly set values without using @Mapping + beneficiarydetail.setAreaId(dto.getAreaId()); + beneficiarydetail.setBeneficiaryRegID(dto.getBeneficiaryRegId()); + beneficiarydetail.setCommunity(dto.getCommunity()); + beneficiarydetail.setLiteracyStatus(dto.getLiteracyStatus()); + beneficiarydetail.setCommunityId(dto.getCommunityId()); + beneficiarydetail.setDob(dto.getDob()); + beneficiarydetail.setEducation(dto.getEducation()); + beneficiarydetail.setEducationId(dto.getEducationId()); + beneficiarydetail.setHealthCareWorkerId(dto.getHealthCareWorkerId()); + beneficiarydetail.setHealthCareWorker(dto.getHealthCareWorker()); + beneficiarydetail.setFatherName(dto.getFatherName()); + beneficiarydetail.setMotherName(dto.getMotherName()); + beneficiarydetail.setFirstName(dto.getFirstName()); + beneficiarydetail.setGender(dto.getGender()); + beneficiarydetail.setGenderId(dto.getGenderId()); + beneficiarydetail.setIncomeStatusId(dto.getIncomeStatusId()); + beneficiarydetail.setIncomeStatus(dto.getIncomeStatus()); + beneficiarydetail.setMonthlyFamilyIncome(dto.getMonthlyFamilyIncome()); + if (dto.getLastName() != null) { + beneficiarydetail.setLastName(dto.getLastName()); + } + beneficiarydetail.setMaritalStatusId(dto.getMaritalStatusId()); + beneficiarydetail.setMaritalStatus(dto.getMaritalStatus()); + beneficiarydetail.setMiddleName(dto.getMiddleName()); + beneficiarydetail.setOccupation(dto.getOccupationName()); + beneficiarydetail.setOccupationId(dto.getOccupationId()); + beneficiarydetail.setPhcId(dto.getPhcId()); + beneficiarydetail.setPlaceOfWork(dto.getPlaceOfWork()); + beneficiarydetail.setPreferredLanguage(dto.getPreferredLanguage()); + beneficiarydetail.setReligion(dto.getReligion()); + if (dto.getReligionId() != null) { + beneficiarydetail.setReligionId(BigInteger.valueOf(dto.getReligionId())); + } + beneficiarydetail.setRemarks(dto.getRemarks()); + beneficiarydetail.setServicePointId(dto.getServicePointId()); + beneficiarydetail.setSourceOfInfo(dto.getSourceOfInfo()); + beneficiarydetail.setSpouseName(dto.getSpouseName()); + beneficiarydetail.setStatus(dto.getStatus()); + beneficiarydetail.setTitle(dto.getTitle()); + beneficiarydetail.setTitleId(dto.getTitleId()); + beneficiarydetail.setZoneId(dto.getZoneId()); + beneficiarydetail.setCreatedBy(dto.getAgentName()); + beneficiarydetail.setCreatedDate(dto.getEventTypeDate()); + beneficiarydetail.setIsHIVPositive(MBeneficiarydetail.setIsHIVPositive(dto.getIsHIVPositive())); + beneficiarydetail.setAgeAtMarriage( + MBeneficiarydetail.getAgeAtMarriageCalc(dto.getDob(), dto.getMarriageDate(), dto.getAgeAtMarriage())); + beneficiarydetail.setMarriageDate( + MBeneficiarydetail.getMarriageDateCalc(dto.getDob(), dto.getMarriageDate(), dto.getAgeAtMarriage())); + if (dto.getOtherFields() != null) { + beneficiarydetail.setOtherFields(dto.getOtherFields()); + } + + return beneficiarydetail; } - - if (mContc.getPhoneNum2() != null) { - mContc.setPhoneNum2(cleanPhoneNumber(mContc.getPhoneNum2())); + + /** + * @param identity + * @return + */ + ArrayDeque queue = new ArrayDeque<>(); + + public BeneficiaryCreateResp createIdentity(IdentityDTO identity) { + logger.info("IdentityService.createIdentity - start"); + + List list = null; + MBeneficiaryregidmapping regMap = null; + synchronized (queue) { + if (queue.isEmpty()) { + logger.info("fetching 10000 rows"); + list = regIdRepo.findTop10000ByProvisionedAndReserved(false, false); + logger.info("Adding SynchronousQueue start-- "); + for (MBeneficiaryregidmapping map : list) { + queue.add(map); + } + logger.info("Adding SynchronousQueue end-- "); + } + regMap = queue.removeFirst(); + } + regMap.setReserved(true); + if (regMap.getCreatedDate() == null) { + SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); + String dateToStoreInDataBase = sdf.format(new Date()); + Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); + regMap.setCreatedDate(ts); + } + + regIdRepo.save(regMap); + + regMap.setProvisioned(true); + + logger.info("IdentityService.createIdentity - saving Address"); + ObjectMapper objectMapper = new ObjectMapper(); + MBeneficiaryaddress mAddr = identityDTOToMBeneficiaryaddress(identity); + // MBeneficiaryaddress mAddr1 = + // mapper.identityDTOToMBeneficiaryaddress(identity); + logger.info("identity.getIsPermAddrSameAsCurrAddr = " + identity.getIsPermAddrSameAsCurrAddr()); + if (Boolean.TRUE.equals(identity.getIsPermAddrSameAsCurrAddr())) { + logger.debug("identity.getCurrentAddress = " + identity.getCurrentAddress()); + mAddr.setPermanentAddress(identity.getCurrentAddress()); + } + + logger.info("identity.getIsPermAddrSameAsEmerAddr = " + identity.getIsPermAddrSameAsEmerAddr()); + if (Boolean.TRUE.equals(identity.getIsPermAddrSameAsEmerAddr())) { + logger.debug("identity.getEmergencyAddress = " + identity.getEmergencyAddress()); + mAddr.setPermanentAddress(identity.getEmergencyAddress()); + } + + logger.info("identity.getIsEmerAddrSameAsCurrAddr = " + identity.getIsEmerAddrSameAsCurrAddr()); + if (Boolean.TRUE.equals(identity.getIsEmerAddrSameAsCurrAddr())) { + logger.debug("identity.getCurrentAddress = " + identity.getCurrentAddress()); + mAddr.setEmergencyAddress(identity.getCurrentAddress()); + } + + logger.info("identity.getIsEmerAddrSameAsPermAddr = " + identity.getIsEmerAddrSameAsPermAddr()); + if (Boolean.TRUE.equals(identity.getIsEmerAddrSameAsPermAddr())) { + logger.debug("identity.getPermanentAddress = " + identity.getPermanentAddress()); + mAddr.setEmergencyAddress(identity.getPermanentAddress()); + } + if (mAddr.getCreatedDate() == null) { + SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); + String dateToStoreInDataBase = sdf.format(new Date()); + Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); + mAddr.setCreatedDate(ts); + } + + mAddr = addressRepo.save(mAddr); + logger.info("IdentityService.createIdentity - Address saved - id = " + mAddr.getBenAddressID()); + + // Update van serial no for data sync + addressRepo.updateVanSerialNo(mAddr.getBenAddressID()); + + MBeneficiaryconsent mConsnt = mapper.identityDTOToDefaultMBeneficiaryconsent(identity, true, false); + logger.info("IdentityService.createIdentity - saving Consent"); + if (mConsnt.getCreatedDate() == null) { + SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); + String dateToStoreInDataBase = sdf.format(new Date()); + Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); + mConsnt.setCreatedDate(ts); + } + mConsnt = consentRepo.save(mConsnt); + logger.info("IdentityService.createIdentity - Consent saved - id = " + mConsnt.getBenConsentID()); + + // Update van serial no for data sync + consentRepo.updateVanSerialNo(mConsnt.getBenConsentID()); + + logger.info("IdentityService.createIdentity - saving Contacts"); + MBeneficiarycontact mContc = identityDTOToMBeneficiarycontact(identity); + + if (mContc.getEmergencyContactNum() != null) { + mContc.setEmergencyContactNum(cleanPhoneNumber(mContc.getEmergencyContactNum())); + } + + if (mContc.getPhoneNum1() != null) { + mContc.setPhoneNum1(cleanPhoneNumber(mContc.getPhoneNum1())); + } + + if (mContc.getPhoneNum2() != null) { + mContc.setPhoneNum2(cleanPhoneNumber(mContc.getPhoneNum2())); + } + if (mContc.getPhoneNum3() != null) { + mContc.setPhoneNum3(cleanPhoneNumber(mContc.getPhoneNum3())); + } + + if (mContc.getPhoneNum4() != null) { + mContc.setPhoneNum4(cleanPhoneNumber(mContc.getPhoneNum4())); + } + if (mContc.getPhoneNum5() != null) { + mContc.setPhoneNum5(cleanPhoneNumber(mContc.getPhoneNum5())); + } + if (mContc.getPreferredSMSPhoneNum() != null) { + mContc.setPreferredSMSPhoneNum(cleanPhoneNumber(mContc.getPreferredSMSPhoneNum())); + } + if (mContc.getPreferredPhoneNum() != null) { + mContc.setPreferredPhoneNum(cleanPhoneNumber(mContc.getPreferredPhoneNum())); + } + + // MBeneficiarycontact mContc = + // mapper.identityDTOToMBeneficiarycontact(identity); + if (mContc.getCreatedDate() == null) { + SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); + String dateToStoreInDataBase = sdf.format(new Date()); + Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); + mContc.setCreatedDate(ts); + } + mContc = contactRepo.save(mContc); + logger.info("IdentityService.createIdentity - Contacts saved - id = " + mContc.getBenContactsID()); + + // Update van serial no for data sync + contactRepo.updateVanSerialNo(mContc.getBenContactsID()); + + logger.info("IdentityService.createIdentity - saving Details"); + // MBeneficiarydetail mDetl = mapper.identityDTOToMBeneficiarydetail(identity); + MBeneficiarydetail mDetl = convertIdentityDTOToMBeneficiarydetail(identity); + + if (mDetl.getCreatedDate() == null) { + SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); + String dateToStoreInDataBase = sdf.format(new Date()); + Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); + mDetl.setCreatedDate(ts); + } + mDetl = detailRepo.save(mDetl); + logger.info("IdentityService.createIdentity - Details saved - id = " + mDetl.getBeneficiaryDetailsId()); + + // Update van serial no for data sync + detailRepo.updateVanSerialNo(mDetl.getBeneficiaryDetailsId()); + + MBeneficiaryAccount bankOBJ = mapper.identityDTOToMBeneficiaryAccount(identity); + if (bankOBJ.getCreatedDate() == null) { + SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); + String dateToStoreInDataBase = sdf.format(new Date()); + Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); + bankOBJ.setCreatedDate(ts); + } + bankOBJ = accountRepo.save(bankOBJ); + // Update van serial no for data sync + accountRepo.updateVanSerialNo(bankOBJ.getBenAccountID()); + + // MBeneficiaryImage benImageOBJ = mapper.identityDTOToMBeneficiaryImage(identity); + MBeneficiaryImage benImageOBJ = identityDTOToMBeneficiaryImage(identity); + + if (benImageOBJ.getCreatedDate() == null) { + SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); + String dateToStoreInDataBase = sdf.format(new Date()); + Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); + benImageOBJ.setCreatedDate(ts); + } + benImageOBJ = imageRepo.save(benImageOBJ); + + // Update van serial no for data sync + imageRepo.updateVanSerialNo(benImageOBJ.getBenImageId()); + + logger.info("IdentityService.createIdentity - saving Mapping"); + MBeneficiarymapping benMapping = mapper.identityDTOToMBeneficiarymapping(identity); + + benMapping.setMBeneficiarycontact(mContc); + benMapping.setMBeneficiaryaddress(mAddr); + benMapping.setMBeneficiaryconsent(mConsnt); + benMapping.setMBeneficiarydetail(mDetl); + benMapping.setMBeneficiaryregidmapping(regMap); + benMapping.setMBeneficiaryImage(benImageOBJ); + benMapping.setMBeneficiaryAccount(bankOBJ); + + regMap.setProviderServiceMapID(identity.getProviderServiceMapId()); + // added columns for data sync + // 17-09-2018 + if (identity.getVanID() != null) { + regMap.setVanID(identity.getVanID()); + } + if (identity.getParkingPlaceId() != null) { + regMap.setParkingPlaceID(identity.getParkingPlaceId()); + } + regMap.setVanSerialNo(regMap.getBenRegId()); + // END + + regIdRepo.save(regMap); + if (benMapping.getCreatedDate() == null) { + SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); + String dateToStoreInDataBase = sdf.format(new Date()); + Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); + benMapping.setCreatedDate(ts); + } + + if (identity.getBenFamilyDTOs().get(0).getVanID() != null) { + benMapping.setVanID(identity.getBenFamilyDTOs().get(0).getVanID()); + } + + benMapping = mappingRepo.save(benMapping); + // Update van serial no for data sync + mappingRepo.updateVanSerialNo(benMapping.getBenMapId()); + + final MBeneficiarymapping benMapping2 = benMapping; + logger.info("IdentityService.createIdentity - saving FamilyMaps"); + List fIdenList = null; + List fList = null; + + // new logic (18-09-2018, Neeraj kumar) + if (null != identity.getBenFamilyDTOs()) { + fIdenList = mapper.identityDTOListToMBeneficiaryfamilymappingList(identity.getBenFamilyDTOs()); + if (fIdenList != null) { + for (MBeneficiaryfamilymapping bfMapping : fIdenList) { + bfMapping.setBenMapId(benMapping2.getBenMapId()); + + if (bfMapping.getVanID() == null && identity.getVanID() != null) { + bfMapping.setVanID(identity.getVanID()); + } + if (bfMapping.getVanID() == null && identity.getBenFamilyDTOs().get(0).getVanID() != null) { + bfMapping.setVanID(identity.getBenFamilyDTOs().get(0).getVanID()); + } + + if (bfMapping.getParkingPlaceID() == null && identity.getParkingPlaceId() != null) { + bfMapping.setParkingPlaceID(identity.getParkingPlaceId()); + } + + if (bfMapping.getAssociatedBenRegId() == null) { + bfMapping.setAssociatedBenRegId(benMapping2.getBenRegId()); + } + } + fList = (List) familyMapRepo.saveAll(fIdenList); + // Update van serial no for data sync + if (fList != null && !fList.isEmpty()) { + for (MBeneficiaryfamilymapping obj : fList) { + familyMapRepo.updateVanSerialNo(obj.getBenFamilyMapId()); + } + } + } + } + + logger.info("IdentityService.createIdentity - FamilyMap saved "); + logger.info("IdentityService.createIdentity - saving Service Map"); + MBeneficiaryservicemapping sMap = mapper.identityDTOToMBeneficiaryservicemapping(identity); + sMap.setBenMapId(benMapping.getBenMapId()); + if (sMap.getCreatedDate() == null) { + SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); + String dateToStoreInDataBase = sdf.format(new Date()); + Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); + sMap.setCreatedDate(ts); + } + sMap = serviceMapRepo.save(sMap); + logger.info("IdentityService.createIdentity - ServiceMap saved - id = " + sMap.getBenServiceMapID()); + + // Update van serial no for data sync + serviceMapRepo.updateVanSerialNo(sMap.getBenServiceMapID()); + + List sList = new ArrayList<>(); + sList.add(sMap); + logger.info("IdentityService.createIdentity - saving Identity"); + List mIdenList2 = new ArrayList<>(); + if (null != identity.getIdentities()) { + List mIdenList = mapper + .identityDTOListToMBeneficiaryidentityList(identity.getIdentities()); + mIdenList.forEach(mIden -> { + mIden.setBenMapId(benMapping2.getBenMapId()); + mIden.setCreatedBy(identity.getAgentName()); + mIden.setCreatedDate(identity.getEventTypeDate()); + + // set new column(vanID, parkingPlaceID) value for data sync + if (identity.getVanID() != null) { + mIden.setVanID(identity.getVanID()); + } + if (identity.getParkingPlaceId() != null) { + mIden.setParkingPlaceID(identity.getParkingPlaceId()); + } + + MBeneficiaryidentity m = identityRepo.save(mIden); + + // Update van serial no for data sync + identityRepo.updateVanSerialNo(m.getBenIdentityId()); + + mIdenList2.add(m); + logger.info("IdentityService.createIdentity - Identity saved - id = " + m.getBenIdentityId()); + }); + } + + // return partialMapper.mBeneficiarymappingToBeneficiaryCreateResp(benMapping); + logger.info("IdentityService.createIdentity - end. id = " + benMapping.getBenMapId()); + + BeneficiaryCreateResp response = partialMapper.mBeneficiarymappingToBeneficiaryCreateResp(benMapping); + + // Trigger async sync to Elasticsearch + if (regMap != null && regMap.getBenRegId() != null) { + logger.info("Triggering Elasticsearch sync for benRegId: {}", regMap.getBenRegId()); + syncService.syncBeneficiaryAsync(regMap.getBenRegId()); + } + + return response; } - if (mContc.getPhoneNum3() != null) { - mContc.setPhoneNum3(cleanPhoneNumber(mContc.getPhoneNum3())); + + private String cleanPhoneNumber(String phoneNumber) { + if (phoneNumber == null || phoneNumber.trim().isEmpty()) { + return phoneNumber; + } + + // Remove +91 prefix + String cleaned = phoneNumber.trim(); + if (cleaned.startsWith("+91")) { + cleaned = cleaned.substring(3); + } else if (cleaned.startsWith("91") && cleaned.length() == 12) { + // Handle case where + is already removed but 91 remains + cleaned = cleaned.substring(2); + } + + return cleaned.trim(); } - - if (mContc.getPhoneNum4() != null) { - mContc.setPhoneNum4(cleanPhoneNumber(mContc.getPhoneNum4())); + + private MBeneficiarydetail convertIdentityDTOToMBeneficiarydetail(IdentityDTO dto) { + MBeneficiarydetail beneficiarydetail = new MBeneficiarydetail(); + beneficiarydetail.setAreaId(dto.getAreaId()); + if (null != dto.getBeneficiaryRegId()) { + beneficiarydetail.setBeneficiaryRegID(BigInteger.valueOf(dto.getBeneficiaryRegId())); + } + beneficiarydetail.setCommunity(dto.getCommunity()); + beneficiarydetail.setCommunityId(dto.getCommunityId()); + beneficiarydetail.setDob(dto.getDob()); + beneficiarydetail.setEducation(dto.getEducation()); + beneficiarydetail.setEducationId(dto.getEducationId()); + beneficiarydetail.setEmergencyRegistration(dto.getEmergencyRegistration()); + beneficiarydetail.setHealthCareWorkerId(dto.getHealthCareWorkerId()); + beneficiarydetail.setHealthCareWorker(dto.getHealthCareWorker()); + beneficiarydetail.setFatherName(dto.getFatherName()); + beneficiarydetail.setMotherName(dto.getMotherName()); + beneficiarydetail.setFirstName(dto.getFirstName()); + beneficiarydetail.setGender(dto.getGender()); + beneficiarydetail.setGenderId(dto.getGenderId()); + beneficiarydetail.setIncomeStatus(dto.getIncomeStatus()); + beneficiarydetail.setMonthlyFamilyIncome(dto.getMonthlyFamilyIncome()); + beneficiarydetail.setIncomeStatusId(dto.getIncomeStatusId()); + beneficiarydetail.setLastName(dto.getLastName()); + beneficiarydetail.setMaritalStatusId(dto.getMaritalStatusId()); + beneficiarydetail.setMaritalStatus(dto.getMaritalStatus()); + beneficiarydetail.setMiddleName(dto.getMiddleName()); + beneficiarydetail.setOccupation(dto.getOccupationName()); + beneficiarydetail.setOccupationId(dto.getOccupationId()); + beneficiarydetail.setPhcId(dto.getPhcId()); + beneficiarydetail.setPlaceOfWork(dto.getPlaceOfWork()); + beneficiarydetail.setPreferredLanguageId(dto.getPreferredLanguageId()); + beneficiarydetail.setPreferredLanguage(dto.getPreferredLanguage()); + beneficiarydetail.setReligion(dto.getReligion()); + if (dto.getFaceEmbedding() != null) { + beneficiarydetail.setFaceEmbedding(dto.getFaceEmbedding().toString()); + } + if (dto.getReligionId() != null) { + beneficiarydetail.setReligionId(BigInteger.valueOf(dto.getReligionId())); + } + beneficiarydetail.setRemarks(dto.getRemarks()); + if (dto.getServicePointId() != null) { + beneficiarydetail.setServicePointId(BigInteger.valueOf(dto.getServicePointId())); + } + beneficiarydetail.setSourceOfInfo(dto.getSourceOfInfo()); + beneficiarydetail.setSpouseName(dto.getSpouseName()); + beneficiarydetail.setStatus(dto.getStatus()); + beneficiarydetail.setTitle(dto.getTitle()); + beneficiarydetail.setTitleId(dto.getTitleId()); + beneficiarydetail.setZoneId(dto.getZoneId()); + beneficiarydetail.setCreatedBy(dto.getAgentName()); + beneficiarydetail.setCreatedDate(dto.getCreatedDate()); + beneficiarydetail.setIsHIVPositive(MBeneficiarydetail.setIsHIVPositive(dto.getIsHIVPositive())); + beneficiarydetail.setAgeAtMarriage( + MBeneficiarydetail.getAgeAtMarriageCalc(dto.getDob(), dto.getMarriageDate(), dto.getAgeAtMarriage())); + beneficiarydetail.setMarriageDate( + MBeneficiarydetail.getMarriageDateCalc(dto.getDob(), dto.getMarriageDate(), dto.getAgeAtMarriage())); + beneficiarydetail.setVanID(dto.getVanID()); + beneficiarydetail.setParkingPlaceID(dto.getParkingPlaceId()); + if (dto.getOtherFields() != null) { + beneficiarydetail.setOtherFields(dto.getOtherFields()); + } + if (dto.getLiteracyStatus() != null) { + beneficiarydetail.setLiteracyStatus(dto.getLiteracyStatus()); + } + return beneficiarydetail; } - if (mContc.getPhoneNum5() != null) { - mContc.setPhoneNum5(cleanPhoneNumber(mContc.getPhoneNum5())); + + private MBeneficiaryImage identityDTOToMBeneficiaryImage(IdentityDTO identity) { + MBeneficiaryImage beneficiaryImage = new MBeneficiaryImage(); + + beneficiaryImage.setBenImage(identity.getBenImage()); + beneficiaryImage.setCreatedBy(identity.getAgentName()); + beneficiaryImage.setCreatedDate(identity.getCreatedDate()); + if (identity.getVanID() != null) { + beneficiaryImage.setVanID(identity.getVanID()); + } + if (identity.getBenFamilyDTOs() != null) { + beneficiaryImage.setVanID(identity.getBenFamilyDTOs().get(0).getVanID()); + } + + beneficiaryImage.setParkingPlaceID(identity.getParkingPlaceId()); + + return beneficiaryImage; } - if (mContc.getPreferredSMSPhoneNum() != null) { - mContc.setPreferredSMSPhoneNum(cleanPhoneNumber(mContc.getPreferredSMSPhoneNum())); - } if (mContc.getPreferredPhoneNum() != null) { - mContc.setPreferredPhoneNum(cleanPhoneNumber(mContc.getPreferredPhoneNum())); + + private MBeneficiarycontact identityDTOToMBeneficiarycontact(IdentityDTO dto) { + MBeneficiarycontact beneficiaryContact = new MBeneficiarycontact(); + if (dto.getContact() != null) { + beneficiaryContact.setPreferredPhoneNum(dto.getContact().getPreferredPhoneNum()); + beneficiaryContact.setPreferredPhoneTyp(dto.getContact().getPreferredPhoneTyp()); + beneficiaryContact.setPreferredSMSPhoneNum(dto.getContact().getPreferredSMSPhoneNum()); + beneficiaryContact.setPreferredSMSPhoneTyp(dto.getContact().getPreferredSMSPhoneTyp()); + beneficiaryContact.setEmergencyContactNum(dto.getContact().getEmergencyContactNum()); + beneficiaryContact.setPhoneNum1(dto.getContact().getPhoneNum1()); + beneficiaryContact.setPhoneTyp1(dto.getContact().getPhoneTyp1()); + beneficiaryContact.setPhoneNum2(dto.getContact().getPhoneNum2()); + beneficiaryContact.setPhoneTyp2(dto.getContact().getPhoneTyp2()); + beneficiaryContact.setPhoneNum3(dto.getContact().getPhoneNum3()); + beneficiaryContact.setPhoneTyp3(dto.getContact().getPhoneTyp3()); + beneficiaryContact.setPhoneNum4(dto.getContact().getPhoneNum4()); + beneficiaryContact.setPhoneTyp4(dto.getContact().getPhoneTyp4()); + beneficiaryContact.setPhoneNum5(dto.getContact().getPhoneNum5()); + beneficiaryContact.setPhoneTyp5(dto.getContact().getPhoneTyp5()); + } + beneficiaryContact.setEmailId(dto.getPreferredEmailId()); + beneficiaryContact.setCreatedBy(dto.getAgentName()); + beneficiaryContact.setCreatedDate(dto.getCreatedDate()); + beneficiaryContact.setVanID(dto.getVanID()); + beneficiaryContact.setParkingPlaceID(dto.getParkingPlaceId()); + return beneficiaryContact; } - - - - - // MBeneficiarycontact mContc = - // mapper.identityDTOToMBeneficiarycontact(identity); - if (mContc.getCreatedDate() == null) { - SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); - String dateToStoreInDataBase = sdf.format(new Date()); - Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); - mContc.setCreatedDate(ts); - } - mContc = contactRepo.save(mContc); - logger.info("IdentityService.createIdentity - Contacts saved - id = " + mContc.getBenContactsID()); - - // Update van serial no for data sync - contactRepo.updateVanSerialNo(mContc.getBenContactsID()); - - logger.info("IdentityService.createIdentity - saving Details"); - // MBeneficiarydetail mDetl = mapper.identityDTOToMBeneficiarydetail(identity); - MBeneficiarydetail mDetl = convertIdentityDTOToMBeneficiarydetail(identity); - - if (mDetl.getCreatedDate() == null) { - SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); - String dateToStoreInDataBase = sdf.format(new Date()); - Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); - mDetl.setCreatedDate(ts); - } - mDetl = detailRepo.save(mDetl); - logger.info("IdentityService.createIdentity - Details saved - id = " + mDetl.getBeneficiaryDetailsId()); - - // Update van serial no for data sync - detailRepo.updateVanSerialNo(mDetl.getBeneficiaryDetailsId()); - - MBeneficiaryAccount bankOBJ = mapper.identityDTOToMBeneficiaryAccount(identity); - if (bankOBJ.getCreatedDate() == null) { - SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); - String dateToStoreInDataBase = sdf.format(new Date()); - Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); - bankOBJ.setCreatedDate(ts); - } - bankOBJ = accountRepo.save(bankOBJ); - // Update van serial no for data sync - accountRepo.updateVanSerialNo(bankOBJ.getBenAccountID()); - - // MBeneficiaryImage benImageOBJ = mapper.identityDTOToMBeneficiaryImage(identity); - MBeneficiaryImage benImageOBJ = identityDTOToMBeneficiaryImage(identity); - - if (benImageOBJ.getCreatedDate() == null) { - SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); - String dateToStoreInDataBase = sdf.format(new Date()); - Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); - benImageOBJ.setCreatedDate(ts); - } - benImageOBJ = imageRepo.save(benImageOBJ); - - // Update van serial no for data sync - imageRepo.updateVanSerialNo(benImageOBJ.getBenImageId()); - - logger.info("IdentityService.createIdentity - saving Mapping"); - MBeneficiarymapping benMapping = mapper.identityDTOToMBeneficiarymapping(identity); - - benMapping.setMBeneficiarycontact(mContc); - benMapping.setMBeneficiaryaddress(mAddr); - benMapping.setMBeneficiaryconsent(mConsnt); - benMapping.setMBeneficiarydetail(mDetl); - benMapping.setMBeneficiaryregidmapping(regMap); - benMapping.setMBeneficiaryImage(benImageOBJ); - benMapping.setMBeneficiaryAccount(bankOBJ); - - regMap.setProviderServiceMapID(identity.getProviderServiceMapId()); - // added columns for data sync - // 17-09-2018 - if (identity.getVanID() != null) - regMap.setVanID(identity.getVanID()); - if (identity.getParkingPlaceId() != null) - regMap.setParkingPlaceID(identity.getParkingPlaceId()); - regMap.setVanSerialNo(regMap.getBenRegId()); - // END - - regIdRepo.save(regMap); - if (benMapping.getCreatedDate() == null) { - SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); - String dateToStoreInDataBase = sdf.format(new Date()); - Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); - benMapping.setCreatedDate(ts); - } - - if (identity.getBenFamilyDTOs().get(0).getVanID() != null) - benMapping.setVanID(identity.getBenFamilyDTOs().get(0).getVanID()); - - benMapping = mappingRepo.save(benMapping); - // Update van serial no for data sync - mappingRepo.updateVanSerialNo(benMapping.getBenMapId()); - - final MBeneficiarymapping benMapping2 = benMapping; - logger.info("IdentityService.createIdentity - saving FamilyMaps"); - List fIdenList = null; - List fList = null; - - // new logic (18-09-2018, Neeraj kumar) - if (null != identity.getBenFamilyDTOs()) { - fIdenList = mapper.identityDTOListToMBeneficiaryfamilymappingList(identity.getBenFamilyDTOs()); - if (fIdenList != null) { - for (MBeneficiaryfamilymapping bfMapping : fIdenList) { - bfMapping.setBenMapId(benMapping2.getBenMapId()); - - if (bfMapping.getVanID() == null && identity.getVanID() != null) - bfMapping.setVanID(identity.getVanID()); - if (bfMapping.getVanID() == null && identity.getBenFamilyDTOs().get(0).getVanID() != null) - bfMapping.setVanID(identity.getBenFamilyDTOs().get(0).getVanID()); - - if (bfMapping.getParkingPlaceID() == null && identity.getParkingPlaceId() != null) - bfMapping.setParkingPlaceID(identity.getParkingPlaceId()); - - if (bfMapping.getAssociatedBenRegId() == null) { - bfMapping.setAssociatedBenRegId(benMapping2.getBenRegId()); - } - } - fList = (List) familyMapRepo.saveAll(fIdenList); - // Update van serial no for data sync - if (fList != null && !fList.isEmpty()) { - for (MBeneficiaryfamilymapping obj : fList) { - familyMapRepo.updateVanSerialNo(obj.getBenFamilyMapId()); - } - } - } - } - - logger.info("IdentityService.createIdentity - FamilyMap saved "); - logger.info("IdentityService.createIdentity - saving Service Map"); - MBeneficiaryservicemapping sMap = mapper.identityDTOToMBeneficiaryservicemapping(identity); - sMap.setBenMapId(benMapping.getBenMapId()); - if (sMap.getCreatedDate() == null) { - SimpleDateFormat sdf = new SimpleDateFormat(CREATED_DATE_FORMAT); - String dateToStoreInDataBase = sdf.format(new Date()); - Timestamp ts = Timestamp.valueOf(dateToStoreInDataBase); - sMap.setCreatedDate(ts); - } - sMap = serviceMapRepo.save(sMap); - logger.info("IdentityService.createIdentity - ServiceMap saved - id = " + sMap.getBenServiceMapID()); - - // Update van serial no for data sync - serviceMapRepo.updateVanSerialNo(sMap.getBenServiceMapID()); - - List sList = new ArrayList<>(); - sList.add(sMap); - logger.info("IdentityService.createIdentity - saving Identity"); - List mIdenList2 = new ArrayList<>(); - if (null != identity.getIdentities()) { - List mIdenList = mapper - .identityDTOListToMBeneficiaryidentityList(identity.getIdentities()); - mIdenList.forEach(mIden -> { - mIden.setBenMapId(benMapping2.getBenMapId()); - mIden.setCreatedBy(identity.getAgentName()); - mIden.setCreatedDate(identity.getEventTypeDate()); - - // set new column(vanID, parkingPlaceID) value for data sync - if (identity.getVanID() != null) - mIden.setVanID(identity.getVanID()); - if (identity.getParkingPlaceId() != null) - mIden.setParkingPlaceID(identity.getParkingPlaceId()); - - MBeneficiaryidentity m = identityRepo.save(mIden); - - // Update van serial no for data sync - identityRepo.updateVanSerialNo(m.getBenIdentityId()); - - mIdenList2.add(m); - logger.info("IdentityService.createIdentity - Identity saved - id = " + m.getBenIdentityId()); - }); - } - - logger.info("IdentityService.createIdentity - end. id = " + benMapping.getBenMapId()); - return partialMapper.mBeneficiarymappingToBeneficiaryCreateResp(benMapping); - } -private String cleanPhoneNumber(String phoneNumber) { - if (phoneNumber == null || phoneNumber.trim().isEmpty()) { - return phoneNumber; + + private MBeneficiaryaddress identityDTOToMBeneficiaryaddress(IdentityDTO dto) { + MBeneficiaryaddress beneficiaryAddress = new MBeneficiaryaddress(); + if (dto.getCurrentAddress() != null) { + beneficiaryAddress.setCurrAddrLine1(dto.getCurrentAddress().getAddrLine1()); + beneficiaryAddress.setCurrAddrLine2(dto.getCurrentAddress().getAddrLine2()); + beneficiaryAddress.setCurrAddrLine3(dto.getCurrentAddress().getAddrLine3()); + beneficiaryAddress.setCurrCountryId(dto.getCurrentAddress().getCountryId()); + beneficiaryAddress.setCurrCountry(dto.getCurrentAddress().getCountry()); + beneficiaryAddress.setCurrStateId(dto.getCurrentAddress().getStateId()); + beneficiaryAddress.setCurrState(dto.getCurrentAddress().getState()); + beneficiaryAddress.setCurrDistrictId(dto.getCurrentAddress().getDistrictId()); + beneficiaryAddress.setCurrDistrict(dto.getCurrentAddress().getDistrict()); + beneficiaryAddress.setCurrSubDistrictId(dto.getCurrentAddress().getSubDistrictId()); + beneficiaryAddress.setCurrSubDistrict(dto.getCurrentAddress().getSubDistrict()); + beneficiaryAddress.setCurrVillageId(dto.getCurrentAddress().getVillageId()); + beneficiaryAddress.setCurrVillage(dto.getCurrentAddress().getVillage()); + beneficiaryAddress.setCurrAddressValue(dto.getCurrentAddress().getAddressValue()); + beneficiaryAddress.setCurrPinCode(dto.getCurrentAddress().getPinCode()); + beneficiaryAddress.setCurrZoneID(dto.getCurrentAddress().getZoneID()); + beneficiaryAddress.setCurrZone(dto.getCurrentAddress().getZoneName()); + beneficiaryAddress.setCurrAreaId(dto.getCurrentAddress().getParkingPlaceID()); + beneficiaryAddress.setCurrArea(dto.getCurrentAddress().getParkingPlaceName()); + beneficiaryAddress.setCurrServicePointId(dto.getCurrentAddress().getServicePointID()); + beneficiaryAddress.setCurrServicePoint(dto.getCurrentAddress().getServicePointName()); + beneficiaryAddress.setCurrHabitation(dto.getCurrentAddress().getHabitation()); + } + if (dto.getEmergencyAddress() != null) { + beneficiaryAddress.setEmerAddrLine1(dto.getEmergencyAddress().getAddrLine1()); + beneficiaryAddress.setEmerAddrLine2(dto.getEmergencyAddress().getAddrLine2()); + beneficiaryAddress.setEmerAddrLine3(dto.getEmergencyAddress().getAddrLine3()); + beneficiaryAddress.setEmerCountryId(dto.getEmergencyAddress().getCountryId()); + beneficiaryAddress.setEmerCountry(dto.getEmergencyAddress().getCountry()); + beneficiaryAddress.setEmerStateId(dto.getEmergencyAddress().getStateId()); + beneficiaryAddress.setEmerState(dto.getEmergencyAddress().getState()); + beneficiaryAddress.setEmerDistrictId(dto.getEmergencyAddress().getDistrictId()); + beneficiaryAddress.setEmerDistrict(dto.getEmergencyAddress().getDistrict()); + beneficiaryAddress.setEmerSubDistrictId(dto.getEmergencyAddress().getSubDistrictId()); + beneficiaryAddress.setEmerSubDistrict(dto.getEmergencyAddress().getSubDistrict()); + beneficiaryAddress.setEmerVillageId(dto.getEmergencyAddress().getVillageId()); + beneficiaryAddress.setEmerVillage(dto.getEmergencyAddress().getVillage()); + beneficiaryAddress.setEmerAddressValue(dto.getEmergencyAddress().getAddressValue()); + beneficiaryAddress.setEmerPinCode(dto.getEmergencyAddress().getPinCode()); + beneficiaryAddress.setEmerZoneID(dto.getEmergencyAddress().getZoneID()); + beneficiaryAddress.setEmerZone(dto.getEmergencyAddress().getZoneName()); + beneficiaryAddress.setEmerAreaId(dto.getEmergencyAddress().getParkingPlaceID()); + beneficiaryAddress.setEmerArea(dto.getEmergencyAddress().getParkingPlaceName()); + beneficiaryAddress.setEmerServicePointId(dto.getEmergencyAddress().getServicePointID()); + beneficiaryAddress.setEmerServicePoint(dto.getEmergencyAddress().getServicePointName()); + beneficiaryAddress.setEmerHabitation(dto.getEmergencyAddress().getHabitation()); + } + + if (dto.getPermanentAddress() != null) { + beneficiaryAddress.setPermAddrLine1(dto.getPermanentAddress().getAddrLine1()); + beneficiaryAddress.setPermAddrLine2(dto.getPermanentAddress().getAddrLine2()); + beneficiaryAddress.setPermAddrLine3(dto.getPermanentAddress().getAddrLine3()); + beneficiaryAddress.setPermCountryId(dto.getPermanentAddress().getCountryId()); + beneficiaryAddress.setPermCountry(dto.getPermanentAddress().getCountry()); + beneficiaryAddress.setPermStateId(dto.getPermanentAddress().getStateId()); + beneficiaryAddress.setPermState(dto.getPermanentAddress().getState()); + beneficiaryAddress.setPermDistrictId(dto.getPermanentAddress().getDistrictId()); + beneficiaryAddress.setPermDistrict(dto.getPermanentAddress().getDistrict()); + beneficiaryAddress.setPermSubDistrictId(dto.getPermanentAddress().getSubDistrictId()); + beneficiaryAddress.setPermSubDistrict(dto.getPermanentAddress().getSubDistrict()); + beneficiaryAddress.setPermVillageId(dto.getPermanentAddress().getVillageId()); + beneficiaryAddress.setPermVillage(dto.getPermanentAddress().getVillage()); + beneficiaryAddress.setPermAddressValue(dto.getPermanentAddress().getAddressValue()); + beneficiaryAddress.setPermPinCode(dto.getPermanentAddress().getPinCode()); + + beneficiaryAddress.setPermZoneID(dto.getPermanentAddress().getZoneID()); + beneficiaryAddress.setPermZone(dto.getPermanentAddress().getZoneName()); + beneficiaryAddress.setPermAreaId(dto.getPermanentAddress().getParkingPlaceID()); + beneficiaryAddress.setPermArea(dto.getPermanentAddress().getParkingPlaceName()); + beneficiaryAddress.setPermServicePointId(dto.getPermanentAddress().getServicePointID()); + beneficiaryAddress.setPermServicePoint(dto.getPermanentAddress().getServicePointName()); + beneficiaryAddress.setPermHabitation(dto.getPermanentAddress().getHabitation()); + } + if (dto.getAgentName() != null) { + beneficiaryAddress.setCreatedBy(dto.getAgentName()); + } + if (dto.getCreatedDate() != null) { + beneficiaryAddress.setCreatedDate(dto.getCreatedDate()); + } + if (dto.getVanID() != null) { + beneficiaryAddress.setVanID(dto.getVanID()); + } + if (dto.getParkingPlaceId() != null) { + beneficiaryAddress.setParkingPlaceID(dto.getParkingPlaceId()); + } + + return beneficiaryAddress; } - - // Remove +91 prefix - String cleaned = phoneNumber.trim(); - if (cleaned.startsWith("+91")) { - cleaned = cleaned.substring(3); - } else if (cleaned.startsWith("91") && cleaned.length() == 12) { - // Handle case where + is already removed but 91 remains - cleaned = cleaned.substring(2); + + public String getReservedIdList() { + + return "success"; } - - return cleaned.trim(); -} - private MBeneficiarydetail convertIdentityDTOToMBeneficiarydetail(IdentityDTO dto) { - MBeneficiarydetail beneficiarydetail = new MBeneficiarydetail(); - beneficiarydetail.setAreaId(dto.getAreaId()); - if (null != dto.getBeneficiaryRegId()) - beneficiarydetail.setBeneficiaryRegID(BigInteger.valueOf(dto.getBeneficiaryRegId())); - beneficiarydetail.setCommunity(dto.getCommunity()); - beneficiarydetail.setCommunityId(dto.getCommunityId()); - beneficiarydetail.setDob(dto.getDob()); - beneficiarydetail.setEducation(dto.getEducation()); - beneficiarydetail.setEducationId(dto.getEducationId()); - beneficiarydetail.setEmergencyRegistration(dto.getEmergencyRegistration()); - beneficiarydetail.setHealthCareWorkerId(dto.getHealthCareWorkerId()); - beneficiarydetail.setHealthCareWorker(dto.getHealthCareWorker()); - beneficiarydetail.setFatherName(dto.getFatherName()); - beneficiarydetail.setMotherName(dto.getMotherName()); - beneficiarydetail.setFirstName(dto.getFirstName()); - beneficiarydetail.setGender(dto.getGender()); - beneficiarydetail.setGenderId(dto.getGenderId()); - beneficiarydetail.setIncomeStatus(dto.getIncomeStatus()); - beneficiarydetail.setMonthlyFamilyIncome(dto.getMonthlyFamilyIncome()); - beneficiarydetail.setIncomeStatusId(dto.getIncomeStatusId()); - beneficiarydetail.setLastName(dto.getLastName()); - beneficiarydetail.setMaritalStatusId(dto.getMaritalStatusId()); - beneficiarydetail.setMaritalStatus(dto.getMaritalStatus()); - beneficiarydetail.setMiddleName(dto.getMiddleName()); - beneficiarydetail.setOccupation(dto.getOccupationName()); - beneficiarydetail.setOccupationId(dto.getOccupationId()); - beneficiarydetail.setPhcId(dto.getPhcId()); - beneficiarydetail.setPlaceOfWork(dto.getPlaceOfWork()); - beneficiarydetail.setPreferredLanguageId(dto.getPreferredLanguageId()); - beneficiarydetail.setPreferredLanguage(dto.getPreferredLanguage()); - beneficiarydetail.setReligion(dto.getReligion()); - if (dto.getFaceEmbedding() != null) - beneficiarydetail.setFaceEmbedding(dto.getFaceEmbedding().toString()); - if (dto.getReligionId() != null) - beneficiarydetail.setReligionId(BigInteger.valueOf(dto.getReligionId())); - beneficiarydetail.setRemarks(dto.getRemarks()); - if (dto.getServicePointId() != null) - beneficiarydetail.setServicePointId(BigInteger.valueOf(dto.getServicePointId())); - beneficiarydetail.setSourceOfInfo(dto.getSourceOfInfo()); - beneficiarydetail.setSpouseName(dto.getSpouseName()); - beneficiarydetail.setStatus(dto.getStatus()); - beneficiarydetail.setTitle(dto.getTitle()); - beneficiarydetail.setTitleId(dto.getTitleId()); - beneficiarydetail.setZoneId(dto.getZoneId()); - beneficiarydetail.setCreatedBy(dto.getAgentName()); - beneficiarydetail.setCreatedDate(dto.getCreatedDate()); - beneficiarydetail.setIsHIVPositive(MBeneficiarydetail.setIsHIVPositive(dto.getIsHIVPositive())); - beneficiarydetail.setAgeAtMarriage( - MBeneficiarydetail.getAgeAtMarriageCalc(dto.getDob(), dto.getMarriageDate(), dto.getAgeAtMarriage())); - beneficiarydetail.setMarriageDate( - MBeneficiarydetail.getMarriageDateCalc(dto.getDob(), dto.getMarriageDate(), dto.getAgeAtMarriage())); - beneficiarydetail.setVanID(dto.getVanID()); - beneficiarydetail.setParkingPlaceID(dto.getParkingPlaceId()); - if(dto.getOtherFields() != null) - beneficiarydetail.setOtherFields(dto.getOtherFields()); - if(dto.getLiteracyStatus() != null) - beneficiarydetail.setLiteracyStatus(dto.getLiteracyStatus()); - return beneficiarydetail; - } - - private MBeneficiaryImage identityDTOToMBeneficiaryImage(IdentityDTO identity) { - MBeneficiaryImage beneficiaryImage = new MBeneficiaryImage(); - - beneficiaryImage.setBenImage(identity.getBenImage()); - beneficiaryImage.setCreatedBy(identity.getAgentName()); - beneficiaryImage.setCreatedDate(identity.getCreatedDate()); - if (identity.getVanID() != null) - beneficiaryImage.setVanID(identity.getVanID()); - if (identity.getBenFamilyDTOs() != null) - beneficiaryImage.setVanID(identity.getBenFamilyDTOs().get(0).getVanID()); - - beneficiaryImage.setParkingPlaceID(identity.getParkingPlaceId()); - - return beneficiaryImage; - } - - private MBeneficiarycontact identityDTOToMBeneficiarycontact(IdentityDTO dto) { - MBeneficiarycontact beneficiaryContact = new MBeneficiarycontact(); - if (dto.getContact() != null) { - beneficiaryContact.setPreferredPhoneNum(dto.getContact().getPreferredPhoneNum()); - beneficiaryContact.setPreferredPhoneTyp(dto.getContact().getPreferredPhoneTyp()); - beneficiaryContact.setPreferredSMSPhoneNum(dto.getContact().getPreferredSMSPhoneNum()); - beneficiaryContact.setPreferredSMSPhoneTyp(dto.getContact().getPreferredSMSPhoneTyp()); - beneficiaryContact.setEmergencyContactNum(dto.getContact().getEmergencyContactNum()); - beneficiaryContact.setPhoneNum1(dto.getContact().getPhoneNum1()); - beneficiaryContact.setPhoneTyp1(dto.getContact().getPhoneTyp1()); - beneficiaryContact.setPhoneNum2(dto.getContact().getPhoneNum2()); - beneficiaryContact.setPhoneTyp2(dto.getContact().getPhoneTyp2()); - beneficiaryContact.setPhoneNum3(dto.getContact().getPhoneNum3()); - beneficiaryContact.setPhoneTyp3(dto.getContact().getPhoneTyp3()); - beneficiaryContact.setPhoneNum4(dto.getContact().getPhoneNum4()); - beneficiaryContact.setPhoneTyp4(dto.getContact().getPhoneTyp4()); - beneficiaryContact.setPhoneNum5(dto.getContact().getPhoneNum5()); - beneficiaryContact.setPhoneTyp5(dto.getContact().getPhoneTyp5()); - } - beneficiaryContact.setEmailId(dto.getPreferredEmailId()); - beneficiaryContact.setCreatedBy(dto.getAgentName()); - beneficiaryContact.setCreatedDate(dto.getCreatedDate()); - beneficiaryContact.setVanID(dto.getVanID()); - beneficiaryContact.setParkingPlaceID(dto.getParkingPlaceId()); - return beneficiaryContact; - } - - private MBeneficiaryaddress identityDTOToMBeneficiaryaddress(IdentityDTO dto) { - MBeneficiaryaddress beneficiaryAddress = new MBeneficiaryaddress(); - if (dto.getCurrentAddress() != null) { - beneficiaryAddress.setCurrAddrLine1(dto.getCurrentAddress().getAddrLine1()); - beneficiaryAddress.setCurrAddrLine2(dto.getCurrentAddress().getAddrLine2()); - beneficiaryAddress.setCurrAddrLine3(dto.getCurrentAddress().getAddrLine3()); - beneficiaryAddress.setCurrCountryId(dto.getCurrentAddress().getCountryId()); - beneficiaryAddress.setCurrCountry(dto.getCurrentAddress().getCountry()); - beneficiaryAddress.setCurrStateId(dto.getCurrentAddress().getStateId()); - beneficiaryAddress.setCurrState(dto.getCurrentAddress().getState()); - beneficiaryAddress.setCurrDistrictId(dto.getCurrentAddress().getDistrictId()); - beneficiaryAddress.setCurrDistrict(dto.getCurrentAddress().getDistrict()); - beneficiaryAddress.setCurrSubDistrictId(dto.getCurrentAddress().getSubDistrictId()); - beneficiaryAddress.setCurrSubDistrict(dto.getCurrentAddress().getSubDistrict()); - beneficiaryAddress.setCurrVillageId(dto.getCurrentAddress().getVillageId()); - beneficiaryAddress.setCurrVillage(dto.getCurrentAddress().getVillage()); - beneficiaryAddress.setCurrAddressValue(dto.getCurrentAddress().getAddressValue()); - beneficiaryAddress.setCurrPinCode(dto.getCurrentAddress().getPinCode()); - beneficiaryAddress.setCurrZoneID(dto.getCurrentAddress().getZoneID()); - beneficiaryAddress.setCurrZone(dto.getCurrentAddress().getZoneName()); - beneficiaryAddress.setCurrAreaId(dto.getCurrentAddress().getParkingPlaceID()); - beneficiaryAddress.setCurrArea(dto.getCurrentAddress().getParkingPlaceName()); - beneficiaryAddress.setCurrServicePointId(dto.getCurrentAddress().getServicePointID()); - beneficiaryAddress.setCurrServicePoint(dto.getCurrentAddress().getServicePointName()); - beneficiaryAddress.setCurrHabitation(dto.getCurrentAddress().getHabitation()); - } - if (dto.getEmergencyAddress() != null) { - beneficiaryAddress.setEmerAddrLine1(dto.getEmergencyAddress().getAddrLine1()); - beneficiaryAddress.setEmerAddrLine2(dto.getEmergencyAddress().getAddrLine2()); - beneficiaryAddress.setEmerAddrLine3(dto.getEmergencyAddress().getAddrLine3()); - beneficiaryAddress.setEmerCountryId(dto.getEmergencyAddress().getCountryId()); - beneficiaryAddress.setEmerCountry(dto.getEmergencyAddress().getCountry()); - beneficiaryAddress.setEmerStateId(dto.getEmergencyAddress().getStateId()); - beneficiaryAddress.setEmerState(dto.getEmergencyAddress().getState()); - beneficiaryAddress.setEmerDistrictId(dto.getEmergencyAddress().getDistrictId()); - beneficiaryAddress.setEmerDistrict(dto.getEmergencyAddress().getDistrict()); - beneficiaryAddress.setEmerSubDistrictId(dto.getEmergencyAddress().getSubDistrictId()); - beneficiaryAddress.setEmerSubDistrict(dto.getEmergencyAddress().getSubDistrict()); - beneficiaryAddress.setEmerVillageId(dto.getEmergencyAddress().getVillageId()); - beneficiaryAddress.setEmerVillage(dto.getEmergencyAddress().getVillage()); - beneficiaryAddress.setEmerAddressValue(dto.getEmergencyAddress().getAddressValue()); - beneficiaryAddress.setEmerPinCode(dto.getEmergencyAddress().getPinCode()); - beneficiaryAddress.setEmerZoneID(dto.getEmergencyAddress().getZoneID()); - beneficiaryAddress.setEmerZone(dto.getEmergencyAddress().getZoneName()); - beneficiaryAddress.setEmerAreaId(dto.getEmergencyAddress().getParkingPlaceID()); - beneficiaryAddress.setEmerArea(dto.getEmergencyAddress().getParkingPlaceName()); - beneficiaryAddress.setEmerServicePointId(dto.getEmergencyAddress().getServicePointID()); - beneficiaryAddress.setEmerServicePoint(dto.getEmergencyAddress().getServicePointName()); - beneficiaryAddress.setEmerHabitation(dto.getEmergencyAddress().getHabitation()); - } - - if (dto.getPermanentAddress() != null) { - beneficiaryAddress.setPermAddrLine1(dto.getPermanentAddress().getAddrLine1()); - beneficiaryAddress.setPermAddrLine2(dto.getPermanentAddress().getAddrLine2()); - beneficiaryAddress.setPermAddrLine3(dto.getPermanentAddress().getAddrLine3()); - beneficiaryAddress.setPermCountryId(dto.getPermanentAddress().getCountryId()); - beneficiaryAddress.setPermCountry(dto.getPermanentAddress().getCountry()); - beneficiaryAddress.setPermStateId(dto.getPermanentAddress().getStateId()); - beneficiaryAddress.setPermState(dto.getPermanentAddress().getState()); - beneficiaryAddress.setPermDistrictId(dto.getPermanentAddress().getDistrictId()); - beneficiaryAddress.setPermDistrict(dto.getPermanentAddress().getDistrict()); - beneficiaryAddress.setPermSubDistrictId(dto.getPermanentAddress().getSubDistrictId()); - beneficiaryAddress.setPermSubDistrict(dto.getPermanentAddress().getSubDistrict()); - beneficiaryAddress.setPermVillageId(dto.getPermanentAddress().getVillageId()); - beneficiaryAddress.setPermVillage(dto.getPermanentAddress().getVillage()); - beneficiaryAddress.setPermAddressValue(dto.getPermanentAddress().getAddressValue()); - beneficiaryAddress.setPermPinCode(dto.getPermanentAddress().getPinCode()); - - beneficiaryAddress.setPermZoneID(dto.getPermanentAddress().getZoneID()); - beneficiaryAddress.setPermZone(dto.getPermanentAddress().getZoneName()); - beneficiaryAddress.setPermAreaId(dto.getPermanentAddress().getParkingPlaceID()); - beneficiaryAddress.setPermArea(dto.getPermanentAddress().getParkingPlaceName()); - beneficiaryAddress.setPermServicePointId(dto.getPermanentAddress().getServicePointID()); - beneficiaryAddress.setPermServicePoint(dto.getPermanentAddress().getServicePointName()); - beneficiaryAddress.setPermHabitation(dto.getPermanentAddress().getHabitation()); - } - if (dto.getAgentName() != null) { - beneficiaryAddress.setCreatedBy(dto.getAgentName()); - } - if (dto.getCreatedDate() != null) { - beneficiaryAddress.setCreatedDate(dto.getCreatedDate()); - } - if (dto.getVanID() != null) { - beneficiaryAddress.setVanID(dto.getVanID()); - } - if (dto.getParkingPlaceId() != null) { - beneficiaryAddress.setParkingPlaceID(dto.getParkingPlaceId()); - } - - return beneficiaryAddress; - } - - public String getReservedIdList() { - - return "success"; - } - - /** - * - * @param reserveIdentityDTO - * @return - */ - public String reserveIdentity(ReserveIdentityDTO reserveIdentityDTO) { - - Long availableCount = regIdRepo.countByProviderServiceMapIDAndVehicalNoOrderByBenRegIdAsc( - reserveIdentityDTO.getProviderServiceMapID(), reserveIdentityDTO.getVehicalNo()); - - if (reserveIdentityDTO.getReserveCount() < availableCount) { - - MBeneficiaryregidmapping beneficiaryregidmapping; - Long countToBeAllocate = reserveIdentityDTO.getReserveCount() - availableCount; - countToBeAllocate++; - for (int index = 0; index < countToBeAllocate; index++) { - - beneficiaryregidmapping = regIdRepo.findFirstByProviderServiceMapIDAndVehicalNoOrderByBenRegIdAsc(null, - null); - beneficiaryregidmapping.setProviderServiceMapID(reserveIdentityDTO.getProviderServiceMapID()); - beneficiaryregidmapping.setVehicalNo(reserveIdentityDTO.getVehicalNo()); - regIdRepo.save(beneficiaryregidmapping); - } - } - - return "Successfully Completed"; - } - - public String unReserveIdentity(ReserveIdentityDTO unReserve) { - - regIdRepo.unreserveBeneficiaryIds(unReserve.getProviderServiceMapID(), unReserve.getVehicalNo()); - return "Successfully Completed"; - } - - /** - * Get partial details of beneficiaries (first name middle name and last name) - * list on benId's list - * - * @param BenRegIds - * @return {@link List} Beneficiaries - */ - - public List getBeneficiariesPartialDeatilsByBenRegIdList(List benRegIds) { - - logger.info("IdentityService.getBeneficiariesPartialDeatilsByBenRegId - end"); - List list = new ArrayList<>(); - - // new logic, 19-12-2018 - List benMapObjArr = null; - if (benRegIds != null && !benRegIds.isEmpty()) { - benMapObjArr = mappingRepo.getBenMappingByRegIDList(benRegIds); - if (benMapObjArr != null && !benMapObjArr.isEmpty()) { - for (Object[] objArr : benMapObjArr) { - MBeneficiarymapping benMap = this.getBeneficiariesDTONewPartial(objArr); - - list.add(partialMapper.mBeneficiarymappingToBeneficiariesPartialDTO(benMap)); - } - } - logger.info("benMap size" + (list.isEmpty() ? "No Beneficiary Found" : list.size())); - - } - // end - logger.info("IdetityService.getBeneficiariesPartialDeatilsByBenRegId - end"); - - return list; - } - - /** - * Get partial details of beneficiaries (first name middle name and last name) - * list on benId's list - * - * @param benRegIds - * @return {@link List} Beneficiaries - */ - public List getBeneficiariesDeatilsByBenRegIdList(List benRegIds) { - - logger.info("IdentityService.getBeneficiariesDeatilsByBenRegIdList - end"); - List list = new ArrayList<>(); - - // new logic, 19-12-2018 - List benMapObjArr = null; - if (benRegIds != null && !benRegIds.isEmpty()) { - benMapObjArr = mappingRepo.getBenMappingByRegIDList(benRegIds); - if (benMapObjArr != null && !benMapObjArr.isEmpty()) { - for (Object[] objArr : benMapObjArr) { - MBeneficiarymapping benMap = this.getBeneficiariesDTONew(objArr); - list.add(this.getBeneficiariesDTO(benMap)); - } - } - logger.info("benMap size" + (list.isEmpty() ? "No Beneficiary Found" : list.size())); - } - // end - logger.info("IdetityService.getBeneficiariesPartialDeatilsByBenRegId - end"); - - return list; - } - - /** - * - * @param benMap - * @return - */ - private BeneficiariesDTO getBeneficiariesDTO(MBeneficiarymapping benMap) { - BeneficiariesDTO bdto = mapper.mBeneficiarymappingToBeneficiariesDTO(benMap); - if (null != benMap && null != benMap.getMBeneficiarydetail() - && !StringUtils.isEmpty(benMap.getMBeneficiarydetail().getFaceEmbedding())) { - String faceEmbedding = benMap.getMBeneficiarydetail().getFaceEmbedding(); - String trimmedInput = faceEmbedding.replaceAll("[\\[\\]]", ""); - List floatList = new ArrayList<>(); - if(!StringUtils.isEmpty(trimmedInput)) { - String[] stringNumbers = trimmedInput.split(",\\s*"); - for (String str : stringNumbers) { - floatList.add(Float.parseFloat(str)); - } - } - bdto.setFaceEmbedding(floatList); - } - // bdto.setOtherFields(benMap.getMBeneficiarydetail().getOtherFields()); - bdto.setBeneficiaryFamilyTags( - mapper.mapToMBeneficiaryfamilymappingWithBenFamilyDTOList(benMap.getMBeneficiaryfamilymappings())); - bdto.setBeneficiaryIdentites( - mapper.mBeneficiaryidentityListToBenIdentityDTOList(benMap.getMBeneficiaryidentities())); - - List abhaList = v_BenAdvanceSearchRepo.getBenAbhaDetailsByBenRegID(bdto.getBenRegId()); - if (abhaList != null && !abhaList.isEmpty()) { - List abhaDTOList = new ArrayList<>(); - AbhaAddressDTO abhaDTO; - for (Object[] objArr : abhaList) { - abhaDTO = new AbhaAddressDTO(); - abhaDTO.setBeneficiaryRegID(bdto.getBenRegId()); - if (objArr[1] != null) - abhaDTO.setHealthID(objArr[1].toString()); - if (objArr[2] != null) - abhaDTO.setHealthIDNumber(objArr[2].toString()); - if (objArr[3] != null) - abhaDTO.setAuthenticationMode(objArr[3].toString()); - if (objArr[4] != null) - abhaDTO.setCreatedDate((Timestamp) objArr[4]); - - abhaDTOList.add(abhaDTO); - - } - bdto.setAbhaDetails(abhaDTOList); - } - return bdto; - } - - private BeneficiariesDTO mBeneficiarymappingToBeneficiariesDTO(MBeneficiarymapping benMap) { - // TODO Auto-generated method stub - return null; - } - - /** - * finite search - * - * @param identityDTO - * @return - */ - public List getBeneficiaries(IdentityDTO identityDTO) { - List list = new ArrayList<>(); - - List benMapList = mappingRepo.finiteSearch(identityDTO); - for (MBeneficiarymapping benMap : benMapList) { - list.add(this.getBeneficiariesDTO(benMap)); - logger.info("benMapId: " + benMap.getBenMapId() + " :: BenId: " - + benMap.getMBeneficiaryregidmapping().getBeneficiaryID()); - } - - return list; - } - - /*** - * - * @return beneficiary image for beneficiary Reg ID. - */ - public String getBeneficiaryImage(String requestOBJ) { - OutputResponse response = new OutputResponse(); - try { - Map benImageMap = new HashMap<>(); - if (requestOBJ != null) { - - JsonElement jsnElmnt = JsonParser.parseString(requestOBJ); - JsonObject obj = jsnElmnt.getAsJsonObject(); - - if (obj != null && obj.has("beneficiaryRegID") && obj.get("beneficiaryRegID") != null) { - MBeneficiarymapping benMap = mappingRepo - .getBenImageIdByBenRegID(obj.get("beneficiaryRegID").getAsBigInteger()); - - if (benMap != null && benMap.getBenImageId() != null && benMap.getVanID() != null) { - MBeneficiaryImage benImageOBJ = imageRepo.getBenImageByBenImageID(benMap.getBenImageId(), - benMap.getVanID()); - benImageMap.put("benImage", benImageOBJ.getBenImage()); - benImageMap.put("createdDate", benImageOBJ.getCreatedDate()); - response.setResponse( - new GsonBuilder().setLongSerializationPolicy(LongSerializationPolicy.STRING).create() - .toJson(benImageMap)); - } else { - response.setResponse("Image not available"); - } - } else { - response.setError(5000, "Invalid request"); - } - } - } catch (Exception e) { - logger.error("Error while beneficiary image fetching" + e); - response.setError(e); - } - - return response.toString(); - } - - public void editIdentityEducationOrCommunity(IdentityEditDTO identity) throws MissingMandatoryFieldsException { - logger.info("IdentityService.editIdentityEducationorCommunity - start"); - if (identity.getBeneficiaryRegId() == null && null == identity.getBeneficaryId()) { - throw new MissingMandatoryFieldsException("Either of BeneficiaryID or Beneficiary Reg Id is mandatory."); - } - - // new logic : 13-11-2018 - MBeneficiarymapping benMapping = mappingRepo.findByBenRegIdOrderByBenMapIdAsc(identity.getBeneficiaryRegId()); - if (benMapping != null && benMapping.getBenDetailsId() != null && benMapping.getVanID() != null) { - if (identity.getCommunityId() != null) { - detailRepo.updateCommunity(benMapping.getBenDetailsId(), benMapping.getVanID(), - identity.getCommunityId()); - } - if (identity.getEducationId() != null) { - detailRepo.updateEducation(benMapping.getBenDetailsId(), benMapping.getVanID(), - identity.getEducationId()); - } - } - - } - - public int importBenIdToLocalServer(List benIdImportDTOList) { - logger.info("IdentityService.importBenIdToLocalServer - start"); - logger.info("IdentityService.importBenIdToLocalServer - benIdImportDTOList size : " - + (benIdImportDTOList == null ? "size:0" : benIdImportDTOList.size())); - if (!benIdImportDTOList.isEmpty()) { - ArrayList mBeneficiaryregidmappingList = benIdImportMapper - .benIdImportDTOToMBeneficiaryregidmappings(benIdImportDTOList); -logger.info("Inside if block of importBenIdToLocalServer"); - jdbcTemplate = getJdbcTemplate(); - List dataList = new ArrayList<>(); - Object[] objArr; - String query = " INSERT INTO m_beneficiaryregidmapping(BenRegId, BeneficiaryID, " - + " Provisioned, CreatedDate, CreatedBy, Reserved) VALUES (?,?,?,?,?,?) "; -logger.info("query : " + query); - for (MBeneficiaryregidmapping obj : mBeneficiaryregidmappingList) { - logger.info("inside for check->",obj); - - logger.info("In for loop of importBenIdToLocalServer"+obj.getVanID()); - objArr = new Object[7]; - - objArr[0] = obj.getBenRegId(); - objArr[1] = obj.getBeneficiaryID(); - objArr[2] = false; - objArr[3] = obj.getCreatedDate(); - objArr[4] = obj.getCreatedBy(); - objArr[5] = false; - objArr[6] = obj.getVanID(); - - dataList.add(objArr); - logger.info("regid :" + obj.getBenRegId() + " - benid :" + obj.getBeneficiaryID()); - } - - int[] i = jdbcTemplate.batchUpdate(query, dataList); - - return i.length; - } else - return 0; - - } - - public Long checkBenIDAvailabilityLocal() { - return regIdRepo.countByProvisioned(false); - - } + + /** + * + * @param reserveIdentityDTO + * @return + */ + public String reserveIdentity(ReserveIdentityDTO reserveIdentityDTO) { + + Long availableCount = regIdRepo.countByProviderServiceMapIDAndVehicalNoOrderByBenRegIdAsc( + reserveIdentityDTO.getProviderServiceMapID(), reserveIdentityDTO.getVehicalNo()); + + if (reserveIdentityDTO.getReserveCount() < availableCount) { + + MBeneficiaryregidmapping beneficiaryregidmapping; + Long countToBeAllocate = reserveIdentityDTO.getReserveCount() - availableCount; + countToBeAllocate++; + for (int index = 0; index < countToBeAllocate; index++) { + + beneficiaryregidmapping = regIdRepo.findFirstByProviderServiceMapIDAndVehicalNoOrderByBenRegIdAsc(null, + null); + beneficiaryregidmapping.setProviderServiceMapID(reserveIdentityDTO.getProviderServiceMapID()); + beneficiaryregidmapping.setVehicalNo(reserveIdentityDTO.getVehicalNo()); + regIdRepo.save(beneficiaryregidmapping); + } + } + + return "Successfully Completed"; + } + + public String unReserveIdentity(ReserveIdentityDTO unReserve) { + + regIdRepo.unreserveBeneficiaryIds(unReserve.getProviderServiceMapID(), unReserve.getVehicalNo()); + return "Successfully Completed"; + } + + /** + * Get partial details of beneficiaries (first name middle name and last + * name) list on benId's list + * + * @param BenRegIds + * @return {@link List} Beneficiaries + */ + public List getBeneficiariesPartialDeatilsByBenRegIdList(List benRegIds) { + + logger.info("IdentityService.getBeneficiariesPartialDeatilsByBenRegId - end"); + List list = new ArrayList<>(); + + // new logic, 19-12-2018 + List benMapObjArr = null; + if (benRegIds != null && !benRegIds.isEmpty()) { + benMapObjArr = mappingRepo.getBenMappingByRegIDList(benRegIds); + if (benMapObjArr != null && !benMapObjArr.isEmpty()) { + for (Object[] objArr : benMapObjArr) { + MBeneficiarymapping benMap = this.getBeneficiariesDTONewPartial(objArr); + + list.add(partialMapper.mBeneficiarymappingToBeneficiariesPartialDTO(benMap)); + } + } + logger.info("benMap size" + (list.isEmpty() ? "No Beneficiary Found" : list.size())); + + } + // end + logger.info("IdetityService.getBeneficiariesPartialDeatilsByBenRegId - end"); + + return list; + } + + /** + * Get partial details of beneficiaries (first name middle name and last + * name) list on benId's list + * + * @param benRegIds + * @return {@link List} Beneficiaries + */ + public List getBeneficiariesDeatilsByBenRegIdList(List benRegIds) { + + logger.info("IdentityService.getBeneficiariesDeatilsByBenRegIdList - end"); + List list = new ArrayList<>(); + + // new logic, 19-12-2018 + List benMapObjArr = null; + if (benRegIds != null && !benRegIds.isEmpty()) { + benMapObjArr = mappingRepo.getBenMappingByRegIDList(benRegIds); + if (benMapObjArr != null && !benMapObjArr.isEmpty()) { + for (Object[] objArr : benMapObjArr) { + MBeneficiarymapping benMap = this.getBeneficiariesDTONew(objArr); + list.add(this.getBeneficiariesDTO(benMap)); + } + } + logger.info("benMap size" + (list.isEmpty() ? "No Beneficiary Found" : list.size())); + } + // end + logger.info("IdetityService.getBeneficiariesPartialDeatilsByBenRegId - end"); + + return list; + } + + /** + * + * @param benMap + * @return + */ + private BeneficiariesDTO getBeneficiariesDTO(MBeneficiarymapping benMap) { + BeneficiariesDTO bdto = mapper.mBeneficiarymappingToBeneficiariesDTO(benMap); + if (null != benMap && null != benMap.getMBeneficiarydetail() + && !StringUtils.isEmpty(benMap.getMBeneficiarydetail().getFaceEmbedding())) { + String faceEmbedding = benMap.getMBeneficiarydetail().getFaceEmbedding(); + String trimmedInput = faceEmbedding.replaceAll("[\\[\\]]", ""); + List floatList = new ArrayList<>(); + if (!StringUtils.isEmpty(trimmedInput)) { + String[] stringNumbers = trimmedInput.split(",\\s*"); + for (String str : stringNumbers) { + floatList.add(Float.parseFloat(str)); + } + } + bdto.setFaceEmbedding(floatList); + } + // bdto.setOtherFields(benMap.getMBeneficiarydetail().getOtherFields()); + bdto.setBeneficiaryFamilyTags( + mapper.mapToMBeneficiaryfamilymappingWithBenFamilyDTOList(benMap.getMBeneficiaryfamilymappings())); + bdto.setBeneficiaryIdentites( + mapper.mBeneficiaryidentityListToBenIdentityDTOList(benMap.getMBeneficiaryidentities())); + + List abhaList = v_BenAdvanceSearchRepo.getBenAbhaDetailsByBenRegID(bdto.getBenRegId()); + if (abhaList != null && !abhaList.isEmpty()) { + List abhaDTOList = new ArrayList<>(); + AbhaAddressDTO abhaDTO; + for (Object[] objArr : abhaList) { + abhaDTO = new AbhaAddressDTO(); + abhaDTO.setBeneficiaryRegID(bdto.getBenRegId()); + if (objArr[1] != null) { + abhaDTO.setHealthID(objArr[1].toString()); + } + if (objArr[2] != null) { + abhaDTO.setHealthIDNumber(objArr[2].toString()); + } + if (objArr[3] != null) { + abhaDTO.setAuthenticationMode(objArr[3].toString()); + } + if (objArr[4] != null) { + abhaDTO.setCreatedDate((Timestamp) objArr[4]); + } + + abhaDTOList.add(abhaDTO); + + } + bdto.setAbhaDetails(abhaDTOList); + } + return bdto; + } + + private BeneficiariesDTO mBeneficiarymappingToBeneficiariesDTO(MBeneficiarymapping benMap) { + // TODO Auto-generated method stub + return null; + } + + /** + * finite search + * + * @param identityDTO + * @return + */ + public List getBeneficiaries(IdentityDTO identityDTO) { + List list = new ArrayList<>(); + + List benMapList = mappingRepo.finiteSearch(identityDTO); + for (MBeneficiarymapping benMap : benMapList) { + list.add(this.getBeneficiariesDTO(benMap)); + logger.info("benMapId: " + benMap.getBenMapId() + " :: BenId: " + + benMap.getMBeneficiaryregidmapping().getBeneficiaryID()); + } + + return list; + } + + /** + * * + * + * @return beneficiary image for beneficiary Reg ID. + */ + public String getBeneficiaryImage(String requestOBJ) { + OutputResponse response = new OutputResponse(); + try { + Map benImageMap = new HashMap<>(); + if (requestOBJ != null) { + + JsonElement jsnElmnt = JsonParser.parseString(requestOBJ); + JsonObject obj = jsnElmnt.getAsJsonObject(); + + if (obj != null && obj.has("beneficiaryRegID") && obj.get("beneficiaryRegID") != null) { + MBeneficiarymapping benMap = mappingRepo + .getBenImageIdByBenRegID(obj.get("beneficiaryRegID").getAsBigInteger()); + + if (benMap != null && benMap.getBenImageId() != null && benMap.getVanID() != null) { + MBeneficiaryImage benImageOBJ = imageRepo.getBenImageByBenImageID(benMap.getBenImageId(), + benMap.getVanID()); + benImageMap.put("benImage", benImageOBJ.getBenImage()); + benImageMap.put("createdDate", benImageOBJ.getCreatedDate()); + response.setResponse( + new GsonBuilder().setLongSerializationPolicy(LongSerializationPolicy.STRING).create() + .toJson(benImageMap)); + } else { + response.setResponse("Image not available"); + } + } else { + response.setError(5000, "Invalid request"); + } + } + } catch (Exception e) { + logger.error("Error while beneficiary image fetching" + e); + response.setError(e); + } + + return response.toString(); + } + + public void editIdentityEducationOrCommunity(IdentityEditDTO identity) throws MissingMandatoryFieldsException { + logger.info("IdentityService.editIdentityEducationorCommunity - start"); + if (identity.getBeneficiaryRegId() == null && null == identity.getBeneficaryId()) { + throw new MissingMandatoryFieldsException("Either of BeneficiaryID or Beneficiary Reg Id is mandatory."); + } + + // new logic : 13-11-2018 + MBeneficiarymapping benMapping = mappingRepo.findByBenRegIdOrderByBenMapIdAsc(identity.getBeneficiaryRegId()); + if (benMapping != null && benMapping.getBenDetailsId() != null && benMapping.getVanID() != null) { + if (identity.getCommunityId() != null) { + detailRepo.updateCommunity(benMapping.getBenDetailsId(), benMapping.getVanID(), + identity.getCommunityId()); + } + if (identity.getEducationId() != null) { + detailRepo.updateEducation(benMapping.getBenDetailsId(), benMapping.getVanID(), + identity.getEducationId()); + } + } + + } + + public int importBenIdToLocalServer(List benIdImportDTOList) { + logger.info("IdentityService.importBenIdToLocalServer - start"); + logger.info("IdentityService.importBenIdToLocalServer - benIdImportDTOList size : " + + (benIdImportDTOList == null ? "size:0" : benIdImportDTOList.size())); + if (!benIdImportDTOList.isEmpty()) { + ArrayList mBeneficiaryregidmappingList = benIdImportMapper + .benIdImportDTOToMBeneficiaryregidmappings(benIdImportDTOList); + logger.info("Inside if block of importBenIdToLocalServer"); + jdbcTemplate = getJdbcTemplate(); + List dataList = new ArrayList<>(); + Object[] objArr; + String query = " INSERT INTO m_beneficiaryregidmapping(BenRegId, BeneficiaryID, " + + " Provisioned, CreatedDate, CreatedBy, Reserved) VALUES (?,?,?,?,?,?) "; + logger.info("query : " + query); + for (MBeneficiaryregidmapping obj : mBeneficiaryregidmappingList) { + logger.info("inside for check->", obj); + + logger.info("In for loop of importBenIdToLocalServer" + obj.getVanID()); + objArr = new Object[7]; + + objArr[0] = obj.getBenRegId(); + objArr[1] = obj.getBeneficiaryID(); + objArr[2] = false; + objArr[3] = obj.getCreatedDate(); + objArr[4] = obj.getCreatedBy(); + objArr[5] = false; + objArr[6] = obj.getVanID(); + + dataList.add(objArr); + logger.info("regid :" + obj.getBenRegId() + " - benid :" + obj.getBeneficiaryID()); + } + + int[] i = jdbcTemplate.batchUpdate(query, dataList); + + return i.length; + } else { + return 0; + } + + } + + public Long checkBenIDAvailabilityLocal() { + return regIdRepo.countByProvisioned(false); + + } + + } + diff --git a/src/main/java/com/iemr/common/identity/service/elasticsearch/BeneficiaryDocumentDataService.java b/src/main/java/com/iemr/common/identity/service/elasticsearch/BeneficiaryDocumentDataService.java new file mode 100644 index 00000000..a076af20 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/service/elasticsearch/BeneficiaryDocumentDataService.java @@ -0,0 +1,399 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ +package com.iemr.common.identity.service.elasticsearch; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import com.iemr.common.identity.data.elasticsearch.BeneficiaryDocument; +import com.iemr.common.identity.repo.BenMappingRepo; +import com.iemr.common.identity.repo.V_BenAdvanceSearchRepo; + +/** + * Optimized service to fetch complete beneficiary data in bulk + * Uses the new complete data query from BenMappingRepo + */ +@Service +public class BeneficiaryDocumentDataService { + + private static final Logger logger = LoggerFactory.getLogger(BeneficiaryDocumentDataService.class); + + @Autowired + private BenMappingRepo mappingRepo; + + @Autowired + private V_BenAdvanceSearchRepo v_BenAdvanceSearchRepo; + + /** + * Fetch multiple beneficiaries with COMPLETE data in ONE query + * This is the KEY method that replaces multiple individual queries + */ + @Transactional(readOnly = true, timeout = 30) + public List getBeneficiariesBatch(List benRegIds) { + if (benRegIds == null || benRegIds.isEmpty()) { + return new ArrayList<>(); + } + + try { + logger.debug("Fetching {} beneficiaries with complete data", benRegIds.size()); + + List results = mappingRepo.findCompleteDataByBenRegIds(benRegIds); + + logger.info("Fetched {} complete beneficiary records", results.size()); + // Batch fetch ABHA details for ALL beneficiaries at once + Map abhaMap = batchFetchAbhaData(benRegIds); + + logger.info("Fetched ABHA details for {} beneficiaries", abhaMap.size()); + + List documents = new ArrayList<>(); + + for (Object[] row : results) { + try { + BeneficiaryDocument doc = mapRowToDocument(row); + if (doc != null && doc.getBenId() != null) { + + AbhaData abhaData = abhaMap.get(doc.getBenRegId()); + if (abhaData != null) { + doc.setHealthID(abhaData.getHealthID()); + doc.setAbhaID(abhaData.getHealthIDNumber()); + doc.setAbhaCreatedDate(abhaData.getAbhaCreatedDate()); + logger.info("Enriched benRegId={} with healthID={}, abhaID={}", + doc.getBenRegId(), doc.getHealthID(), doc.getAbhaID()); + } else { + logger.debug("No ABHA details for benRegId={}", doc.getBenRegId()); + } + documents.add(doc); + } + } catch (Exception e) { + logger.error("Error mapping row to document: {}", e.getMessage(), e); + } + } + + logger.debug("Successfully converted {} beneficiaries to documents", documents.size()); + return documents; + + } catch (Exception e) { + logger.error("Error fetching beneficiaries batch: {}", e.getMessage(), e); + return new ArrayList<>(); + } + } + + private Map batchFetchAbhaData(List benRegIds) { + try { + return batchFetchAbhaDetails(benRegIds); + } catch (Exception e) { + logger.warn("Error fetching ABHA details (will continue without ABHA data): {}", e.getMessage()); + return new HashMap<>(); // Return empty map to continue processing + } + } + + /** + * Batch fetch ABHA details for multiple beneficiaries + * Returns a map of benRegId -> AbhaData + */ + @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true, timeout = 30) + private Map batchFetchAbhaDetails(List benRegIds) { + Map abhaMap = new HashMap<>(); + + try { + if (benRegIds == null || benRegIds.isEmpty()) { + logger.info("No beneficiary IDs provided for ABHA fetch"); + return abhaMap; + } + logger.debug("Batch fetching ABHA details for {} beneficiaries", benRegIds.size()); + + List abhaRecords = null; + try { + abhaRecords = v_BenAdvanceSearchRepo.getBenAbhaDetailsByBenRegIDs(benRegIds); + } catch (Exception e) { + logger.warn("ABHA query returned error (likely no records): {}", e.getMessage()); + return abhaMap; // Return empty map - this is OK + } + + if (abhaRecords == null || abhaRecords.isEmpty()) { + logger.debug("No ABHA records found for this batch (this is normal)"); + return abhaMap; + } + + logger.debug("Retrieved {} ABHA records", abhaRecords.size()); + + for (Object[] record : abhaRecords) { + try { + // record[0] -> BeneficiaryRegID + // record[1] -> HealthID (ABHA Address) + // record[2] -> HealthIDNumber (ABHA Number) + // record[3] -> AuthenticationMode + // record[4] -> CreatedDate + + Long benRegId = null; + if (record[0] instanceof BigInteger) { + benRegId = ((BigInteger) record[0]).longValue(); + } else if (record[0] instanceof Long) { + benRegId = (Long) record[0]; + } else if (record[0] instanceof Integer) { + benRegId = ((Integer) record[0]).longValue(); + } + + if (benRegId != null && !abhaMap.containsKey(benRegId)) { + // Only store the first (most recent) record for each beneficiary + AbhaData abhaData = new AbhaData(); + abhaData.setHealthID(record[1] != null ? record[1].toString() : null); + abhaData.setHealthIDNumber(record[2] != null ? record[2].toString() : null); + abhaData.setAuthenticationMode(record[3] != null ? record[3].toString() : null); + abhaData.setAbhaCreatedDate(record[4] != null ? record[4].toString() : null); + + abhaMap.put(benRegId, abhaData); + } + } catch (Exception e) { + logger.error("Error processing ABHA record: {}", e.getMessage()); + } + } + + logger.debug("Processed {} unique ABHA records into map", abhaMap.size()); + + } catch (Exception e) { + logger.error("Error batch fetching ABHA details: {}", e.getMessage(), e); + } + + return abhaMap; + } + + /** + * Single beneficiary fetch (for backward compatibility) + */ + @Transactional(readOnly = true, timeout = 10) + public BeneficiaryDocument getBeneficiaryFromDatabase(BigInteger benRegId) { + List ids = new ArrayList<>(); + ids.add(benRegId); + + List results = getBeneficiariesBatch(ids); + return results.isEmpty() ? null : results.get(0); + } + + /** + * Fetch single beneficiary WITH fresh ABHA data + * Use this for real-time sync (create/update operations) + */ + @Transactional(readOnly = true, timeout = 10) + public BeneficiaryDocument getBeneficiaryWithAbhaDetails(BigInteger benRegId) { + if (benRegId == null) { + return null; + } + // Fetch beneficiary + ABHA in one call + List ids = List.of(benRegId); + List results = getBeneficiariesBatch(ids); + + if (results.isEmpty()) { + logger.warn("No beneficiary found for benRegId={}", benRegId); + return null; + } + + BeneficiaryDocument doc = results.get(0); + + // Log for debugging + if (doc.getHealthID() != null || doc.getAbhaID() != null) { + logger.info("Beneficiary has ABHA: benRegId={}, healthID={}, abhaID={}", + benRegId, doc.getHealthID(), doc.getAbhaID()); + } else { + logger.debug("No ABHA details for benRegId={}", benRegId); + } + + return doc; + } + + /** + * Map database row to BeneficiaryDocument for ES + * Matches the query column order from BenMappingRepo + */ + private BeneficiaryDocument mapRowToDocument(Object[] row) { + BeneficiaryDocument doc = new BeneficiaryDocument(); + + try { + int idx = 0; + + // Basic IDs (0-1) + Long benRegId = getLong(row[idx++]); + doc.setBenRegId(benRegId); + String beneficiaryID = getString(row[idx++]); + if (beneficiaryID != null && !beneficiaryID.isEmpty()) { + doc.setBenId(beneficiaryID); + } + doc.setBeneficiaryID(beneficiaryID); + + doc.setFirstName(getString(row[idx++])); + doc.setMiddleName(getString(row[idx++])); + doc.setLastName(getString(row[idx++])); + doc.setGenderID(getInteger(row[idx++])); + doc.setGenderName(getString(row[idx++])); + doc.setGender(doc.getGenderName()); + doc.setDOB(getDate(row[idx++])); + doc.setAge(getInteger(row[idx++])); + doc.setFatherName(getString(row[idx++])); + doc.setSpouseName(getString(row[idx++])); + + doc.setMaritalStatusID(getInteger(row[idx++])); + doc.setMaritalStatusName(getString(row[idx++])); + doc.setIsHIVPos(getString(row[idx++])); + + doc.setCreatedBy(getString(row[idx++])); + doc.setCreatedDate(getDate(row[idx++])); + doc.setLastModDate(getLong(row[idx++])); + doc.setBenAccountID(getLong(row[idx++])); + + doc.setPhoneNum(getString(row[idx++])); + doc.setFamilyID(getString(row[idx++])); + + doc.setStateID(getInteger(row[idx++])); + doc.setStateName(getString(row[idx++])); + doc.setDistrictID(getInteger(row[idx++])); + doc.setDistrictName(getString(row[idx++])); + doc.setBlockID(getInteger(row[idx++])); + doc.setBlockName(getString(row[idx++])); + doc.setVillageID(getInteger(row[idx++])); + doc.setVillageName(getString(row[idx++])); + doc.setPinCode(getString(row[idx++])); + doc.setServicePointID(getInteger(row[idx++])); + doc.setServicePointName(getString(row[idx++])); + doc.setParkingPlaceID(getInteger(row[idx++])); + + doc.setPermStateID(getInteger(row[idx++])); + doc.setPermStateName(getString(row[idx++])); + doc.setPermDistrictID(getInteger(row[idx++])); + doc.setPermDistrictName(getString(row[idx++])); + doc.setPermBlockID(getInteger(row[idx++])); + doc.setPermBlockName(getString(row[idx++])); + doc.setPermVillageID(getInteger(row[idx++])); + doc.setPermVillageName(getString(row[idx++])); + + // doc.setGovtIdentityNo(getString(row[idx++])); + // String aadhar = getString(row[idx]); + // doc.setAadharNo(aadhar != null ? aadhar : doc.getGovtIdentityNo()); + + } catch (Exception e) { + logger.error("Error mapping row to document: {}", e.getMessage(), e); + } + + return doc; + } + + // Helper methods + private String getString(Object value) { + return value != null ? value.toString() : null; + } + + private Long getLong(Object value) { + if (value == null) + return null; + if (value instanceof Long) + return (Long) value; + if (value instanceof Integer) + return ((Integer) value).longValue(); + if (value instanceof BigInteger) + return ((BigInteger) value).longValue(); + try { + return Long.parseLong(value.toString()); + } catch (NumberFormatException e) { + return null; + } + } + + private Integer getInteger(Object value) { + if (value == null) + return null; + if (value instanceof Integer) + return (Integer) value; + if (value instanceof Long) + return ((Long) value).intValue(); + if (value instanceof BigInteger) + return ((BigInteger) value).intValue(); + try { + return Integer.parseInt(value.toString()); + } catch (NumberFormatException e) { + return null; + } + } + + private java.util.Date getDate(Object value) { + if (value == null) + return null; + if (value instanceof java.util.Date) + return (java.util.Date) value; + if (value instanceof java.sql.Timestamp) + return new java.util.Date(((java.sql.Timestamp) value).getTime()); + if (value instanceof java.sql.Date) + return new java.util.Date(((java.sql.Date) value).getTime()); + return null; + } + + /** + * Inner class to hold ABHA data + */ + private static class AbhaData { + private String healthID; + private String healthIDNumber; + private String authenticationMode; + private String abhaCreatedDate; + + public String getHealthID() { + return healthID; + } + + public void setHealthID(String healthID) { + this.healthID = healthID; + } + + public String getHealthIDNumber() { + return healthIDNumber; + } + + public void setHealthIDNumber(String healthIDNumber) { + this.healthIDNumber = healthIDNumber; + } + + public String getAuthenticationMode() { + return authenticationMode; + } + + public void setAuthenticationMode(String authenticationMode) { + this.authenticationMode = authenticationMode; + } + + public String getAbhaCreatedDate() { + return abhaCreatedDate; + } + + public void setAbhaCreatedDate(String abhaCreatedDate) { + this.abhaCreatedDate = abhaCreatedDate; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/service/elasticsearch/BeneficiaryElasticsearchIndexService.java b/src/main/java/com/iemr/common/identity/service/elasticsearch/BeneficiaryElasticsearchIndexService.java new file mode 100644 index 00000000..db445bd4 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/service/elasticsearch/BeneficiaryElasticsearchIndexService.java @@ -0,0 +1,407 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.service.elasticsearch; + +import java.math.BigInteger; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.core.BulkRequest; +import co.elastic.clients.elasticsearch.core.BulkResponse; +import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem; + +import com.iemr.common.identity.data.elasticsearch.BeneficiaryDocument; +import com.iemr.common.identity.data.elasticsearch.ElasticsearchSyncJob; +import com.iemr.common.identity.repo.elasticsearch.SyncJobRepo; + +@Service +public class BeneficiaryElasticsearchIndexService { + + private static final Logger logger = LoggerFactory.getLogger(BeneficiaryElasticsearchIndexService.class); + private static final int BATCH_SIZE = 2000; + private static final int ES_BULK_SIZE = 5000; + private static final int STATUS_UPDATE_FREQUENCY = 5; + + @Autowired + private ElasticsearchClient esClient; + + @Autowired + private BeneficiaryTransactionHelper transactionalWrapper; + + @Autowired + private BeneficiaryDocumentDataService dataService; + + @Autowired + private SyncJobRepo syncJobRepository; + + @Value("${elasticsearch.index.beneficiary}") + private String beneficiaryIndex; + + /** + * Start async full sync job with COMPLETE 38+ field data + */ + @Async("elasticsearchSyncExecutor") + public void syncAllBeneficiariesAsync(Long jobId, String triggeredBy) { + logger.info("Starting ASYNC full sync with COMPLETE data: jobId={}", jobId); + + ElasticsearchSyncJob job = syncJobRepository.findByJobId(jobId) + .orElseThrow(() -> new RuntimeException("Job not found: " + jobId)); + + try { + boolean isResume = false; + int offset = 0; + long processedCount = 0; + long successCount = 0; + long failureCount = 0; + + // Check if this is a resume from a previous run + if (job.getStatus().equals("RUNNING") && job.getCurrentOffset() != null && job.getCurrentOffset() > 0) { + isResume = true; + offset = job.getCurrentOffset(); + processedCount = job.getProcessedRecords() != null ? job.getProcessedRecords() : 0; + successCount = job.getSuccessCount() != null ? job.getSuccessCount() : 0; + failureCount = job.getFailureCount() != null ? job.getFailureCount() : 0; + + logger.info("RESUMING SYNC from offset {} (processed: {}, success: {}, failed: {})", + offset, processedCount, successCount, failureCount); + } else { + job.setStatus("RUNNING"); + job.setStartedAt(new Timestamp(System.currentTimeMillis())); + syncJobRepository.save(job); + } + + long totalCount; + if (job.getTotalRecords() != null && job.getTotalRecords() > 0) { + totalCount = job.getTotalRecords(); + logger.info("Using cached total count: {}", totalCount); + } else { + totalCount = transactionalWrapper.countActiveBeneficiaries(); + job.setTotalRecords(totalCount); + syncJobRepository.save(job); + logger.info("Fetched total beneficiaries to sync: {}", totalCount); + } + + if (totalCount == 0) { + job.setStatus("COMPLETED"); + job.setCompletedAt(new Timestamp(System.currentTimeMillis())); + job.setErrorMessage("No beneficiaries found to sync"); + syncJobRepository.save(job); + return; + } + + List esBatch = new ArrayList<>(); + int batchCounter = offset / BATCH_SIZE; + long startTime = isResume ? job.getStartedAt().getTime() : System.currentTimeMillis(); + long lastProgressUpdate = System.currentTimeMillis(); + int consecutiveErrors = 0; + final int MAX_CONSECUTIVE_ERRORS = 5; + + // Process in batches + while (offset < totalCount) { + try { + logger.info("=== BATCH {} START: offset={}/{} ({:.1f}%) ===", + batchCounter + 1, offset, totalCount, (offset * 100.0 / totalCount)); + + logger.debug("Calling getBeneficiaryIdsBatch(offset={}, limit={})", offset, BATCH_SIZE); + List batchIds = transactionalWrapper.getBeneficiaryIdsBatch(offset, BATCH_SIZE); + + logger.info("Retrieved {} IDs from database", batchIds != null ? batchIds.size() : 0); + + if (batchIds == null || batchIds.isEmpty()) { + logger.warn("No more batches to process at offset {}", offset); + break; + } + + // Reset consecutive error counter on successful fetch + consecutiveErrors = 0; + + logger.debug("Converting {} IDs to BigInteger", batchIds.size()); + List benRegIds = batchIds.stream() + .map(arr -> toBigInteger(arr[0])) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + logger.info("Converted {} valid BigInteger IDs", benRegIds.size()); + + if (benRegIds.isEmpty()) { + logger.error("No valid IDs in batch at offset {} - ALL IDs FAILED CONVERSION", offset); + offset += BATCH_SIZE; + continue; + } + + logger.info("Fetching complete data for {} beneficiaries...", benRegIds.size()); + List documents = dataService.getBeneficiariesBatch(benRegIds); + + logger.info("✓ Fetched {} complete documents for batch at offset {}", documents.size(), offset); + + int docsAddedInThisBatch = 0; + for (BeneficiaryDocument doc : documents) { + try { + if (doc != null && doc.getBenId() != null) { + esBatch.add(doc); + docsAddedInThisBatch++; + + if (esBatch.size() >= ES_BULK_SIZE) { + logger.info("ES batch full ({} docs), indexing now...", esBatch.size()); + int indexed = bulkIndexDocuments(esBatch); + successCount += indexed; + failureCount += (esBatch.size() - indexed); + processedCount += esBatch.size(); + + logger.info("✓ Indexed {}/{} documents successfully", indexed, esBatch.size()); + esBatch.clear(); + } + } else { + logger.warn("Skipping document - doc null: {}, benId null: {}", + doc == null, doc != null ? (doc.getBenId() == null) : "N/A"); + failureCount++; + processedCount++; + } + } catch (Exception e) { + logger.error("Error processing single document: {}", e.getMessage(), e); + failureCount++; + processedCount++; + } + } + + logger.info("Added {} documents to ES batch in this iteration", docsAddedInThisBatch); + + int notFetched = benRegIds.size() - documents.size(); + if (notFetched > 0) { + failureCount += notFetched; + processedCount += notFetched; + logger.warn("{} beneficiaries not fetched from database", notFetched); + } + + offset += BATCH_SIZE; + batchCounter++; + + logger.info("=== BATCH {} END: Processed={}, Success={}, Failed={} ===", + batchCounter, processedCount, successCount, failureCount); + + // Save progress every batch for resume capability + long now = System.currentTimeMillis(); + if (batchCounter % STATUS_UPDATE_FREQUENCY == 0 || (now - lastProgressUpdate) > 30000) { + logger.info("Saving checkpoint for resume capability..."); + updateJobProgress(job, processedCount, successCount, failureCount, + offset, totalCount, startTime); + lastProgressUpdate = now; + } + + // Brief pause every 10 batches + if (batchCounter % 10 == 0) { + logger.debug("Pausing for 500ms after {} batches", batchCounter); + Thread.sleep(500); + } + + } catch (Exception e) { + consecutiveErrors++; + logger.error("!!! ERROR #{} in batch at offset {}: {} !!!", + consecutiveErrors, offset, e.getMessage(), e); + logger.error("Exception type: {}", e.getClass().getName()); + + // Save progress before handling error + job.setCurrentOffset(offset); + job.setProcessedRecords(processedCount); + job.setSuccessCount(successCount); + job.setFailureCount(failureCount); + syncJobRepository.save(job); + + // If too many consecutive errors, mark job as STALLED for manual intervention + if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) { + logger.error("TOO MANY CONSECUTIVE ERRORS ({}). Marking job as STALLED.", consecutiveErrors); + job.setStatus("STALLED"); + job.setErrorMessage("Too many consecutive errors at offset " + offset + ": " + e.getMessage()); + syncJobRepository.save(job); + return; + } + + // Skip this batch and continue + offset += BATCH_SIZE; + + // Exponential backoff: wait longer after each error + long waitTime = Math.min(10000, 1000 * (long) Math.pow(2, consecutiveErrors)); + logger.info("Waiting {}ms before retry...", waitTime); + Thread.sleep(waitTime); + } + } + + // Index remaining documents + if (!esBatch.isEmpty()) { + logger.info("Indexing final batch of {} documents", esBatch.size()); + int indexed = bulkIndexDocuments(esBatch); + successCount += indexed; + failureCount += (esBatch.size() - indexed); + processedCount += esBatch.size(); + } + + // Mark as COMPLETED + job.setStatus("COMPLETED"); + job.setCompletedAt(new Timestamp(System.currentTimeMillis())); + job.setProcessedRecords(processedCount); + job.setSuccessCount(successCount); + job.setFailureCount(failureCount); + job.setCurrentOffset((int) totalCount); + + long duration = System.currentTimeMillis() - startTime; + job.setProcessingSpeed(processedCount / (duration / 1000.0)); + + syncJobRepository.save(job); + + logger.info("Async sync job COMPLETED: jobId={}", jobId); + logger.info("Total: {}, Processed: {}, Success: {}, Failed: {}", + totalCount, processedCount, successCount, failureCount); + logger.info("All 38+ beneficiary fields synced to Elasticsearch!"); + + } catch (Exception e) { + logger.error("CRITICAL ERROR in async sync: jobId={}, error={}", jobId, e.getMessage(), e); + + job.setStatus("FAILED"); + job.setCompletedAt(new Timestamp(System.currentTimeMillis())); + job.setErrorMessage(e.getMessage()); + syncJobRepository.save(job); + } + } + + // Resume a stalled job + public void resumeStalledJob(Long jobId) { + logger.info("Attempting to resume stalled job: {}", jobId); + + ElasticsearchSyncJob job = syncJobRepository.findByJobId(jobId) + .orElseThrow(() -> new RuntimeException("Job not found: " + jobId)); + + if (!job.getStatus().equals("STALLED") && !job.getStatus().equals("RUNNING")) { + throw new RuntimeException("Cannot resume job with status: " + job.getStatus()); + } + + // Reset error counter and continue from last checkpoint + logger.info("Resuming from offset: {}", job.getCurrentOffset()); + syncAllBeneficiariesAsync(jobId, "AUTO_RESUME"); + } + + /** + * Helper method to safely convert various numeric types to BigInteger + * CRITICAL: Native SQL queries return Long, not BigInteger + */ + private BigInteger toBigInteger(Object value) { + if (value == null) { + logger.warn("Attempted to convert null value to BigInteger"); + return null; + } + + try { + if (value instanceof BigInteger) { + return (BigInteger) value; + } + if (value instanceof Long) { + return BigInteger.valueOf((Long) value); + } + if (value instanceof Integer) { + return BigInteger.valueOf(((Integer) value).longValue()); + } + if (value instanceof Number) { + return BigInteger.valueOf(((Number) value).longValue()); + } + return new BigInteger(value.toString()); + } catch (NumberFormatException e) { + logger.error("Cannot convert '{}' (type: {}) to BigInteger: {}", + value, value.getClass().getName(), e.getMessage()); + return null; + } + } + + private void updateJobProgress(ElasticsearchSyncJob job, long processed, long success, + long failure, int offset, long total, long startTime) { + job.setProcessedRecords(processed); + job.setSuccessCount(success); + job.setFailureCount(failure); + job.setCurrentOffset(offset); + + long elapsedTime = System.currentTimeMillis() - startTime; + double speed = elapsedTime > 0 ? processed / (elapsedTime / 1000.0) : 0; + job.setProcessingSpeed(speed); + + if (speed > 0) { + long remaining = total - processed; + long estimatedSeconds = (long) (remaining / speed); + job.setEstimatedTimeRemaining(estimatedSeconds); + } + + syncJobRepository.save(job); + + } + + private int bulkIndexDocuments(List documents) { + if (documents == null || documents.isEmpty()) { + return 0; + } + + try { + BulkRequest.Builder br = new BulkRequest.Builder(); + + for (BeneficiaryDocument doc : documents) { + if (doc.getBenId() != null) { + br.operations(op -> op + .index(idx -> idx + .index(beneficiaryIndex) + .id(doc.getBenId()) + .document(doc))); + } + } + + BulkResponse result = esClient.bulk(br.build()); + int successCount = 0; + + if (result.errors()) { + for (BulkResponseItem item : result.items()) { + if (item.error() == null) { + successCount++; + } else { + logger.error("ES indexing error for doc {}: {}", + item.id(), item.error().reason()); + } + } + } else { + successCount = documents.size(); + } + + logger.debug("Bulk indexed {} documents successfully", successCount); + return successCount; + + } catch (Exception e) { + logger.error("Error in bulk indexing: {}", e.getMessage(), e); + return 0; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/service/elasticsearch/BeneficiaryElasticsearchIndexUpdater.java b/src/main/java/com/iemr/common/identity/service/elasticsearch/BeneficiaryElasticsearchIndexUpdater.java new file mode 100644 index 00000000..ff2fec5b --- /dev/null +++ b/src/main/java/com/iemr/common/identity/service/elasticsearch/BeneficiaryElasticsearchIndexUpdater.java @@ -0,0 +1,115 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.service.elasticsearch; + +import java.math.BigInteger; +import java.util.concurrent.CompletableFuture; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.Refresh; +import co.elastic.clients.elasticsearch.core.DeleteRequest; +import co.elastic.clients.elasticsearch.core.IndexRequest; + +import com.iemr.common.identity.data.elasticsearch.BeneficiaryDocument; + +/** + * Service for real-time Elasticsearch synchronization + * Triggers automatically when beneficiaries are created/updated in database + */ +@Service +public class BeneficiaryElasticsearchIndexUpdater { + + private static final Logger logger = LoggerFactory.getLogger(BeneficiaryElasticsearchIndexUpdater.class); + + @Autowired + private ElasticsearchClient esClient; + + @Autowired + private BeneficiaryDocumentDataService dataService; + + @Value("${elasticsearch.index.beneficiary}") + private String beneficiaryIndex; + + @Value("${elasticsearch.enabled}") + private boolean esEnabled; + + /** + * Delete beneficiary from Elasticsearch + */ + @Async("elasticsearchSyncExecutor") + public void deleteBeneficiaryAsync(String benId) { + if (!esEnabled) { + logger.debug("Elasticsearch is disabled, skipping delete"); + return; + } + + try { + logger.info("Starting async delete for benId: {}", benId); + + DeleteRequest request = DeleteRequest.of(d -> d + .index(beneficiaryIndex) + .id(benId)); + + esClient.delete(request); + + logger.info("Successfully deleted beneficiary from Elasticsearch: benId={}", benId); + + } catch (Exception e) { + logger.error("Error deleting beneficiary {} from Elasticsearch: {}", benId, e.getMessage(), e); + } + } + + @Async + public CompletableFuture syncBeneficiaryAsync(BigInteger benRegId) { + BeneficiaryDocument document = dataService.getBeneficiaryWithAbhaDetails(benRegId); + if (document == null) { + logger.warn("No data found for benRegId: {}", benRegId); + return CompletableFuture.completedFuture(null); + } + + // Log ABHA for verification + logger.info("Syncing benRegId={} with ABHA: healthID={}, abhaID={}", + benRegId, document.getHealthID(), document.getAbhaID()); + + try{ + // Index to ES + esClient.index(i -> i + .index(beneficiaryIndex) + .id(String.valueOf(benRegId)) + .document(document).refresh(Refresh.True)); + + logger.info("Successfully synced benRegId: {} to ES", benRegId); + } catch (Exception e) { + logger.error("Error syncing beneficiary {} to Elasticsearch: {}", benRegId, e.getMessage(), e); + } + return CompletableFuture.completedFuture(null); + + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/service/elasticsearch/BeneficiaryTransactionHelper.java b/src/main/java/com/iemr/common/identity/service/elasticsearch/BeneficiaryTransactionHelper.java new file mode 100644 index 00000000..a173e327 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/service/elasticsearch/BeneficiaryTransactionHelper.java @@ -0,0 +1,89 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.service.elasticsearch; + +import java.math.BigInteger; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import com.iemr.common.identity.repo.BenMappingRepo; + +/** + * Wrapper service to handle database operations with proper transaction management + * This prevents connection timeout issues during long-running sync operations + */ +@Service +public class BeneficiaryTransactionHelper { + + private static final Logger logger = LoggerFactory.getLogger(BeneficiaryTransactionHelper.class); + + @Autowired + private BenMappingRepo mappingRepo; + + /** + * Get beneficiary IDs in a new transaction + * This ensures connection is fresh for each batch + */ + @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true, timeout = 30) + public List getBeneficiaryIdsBatch(int offset, int limit) { + try { + return mappingRepo.getBeneficiaryIdsBatch(offset, limit); + } catch (Exception e) { + logger.error("Error fetching batch: offset={}, limit={}, error={}", + offset, limit, e.getMessage()); + throw e; + } + } + + /** + * Count beneficiaries in a new transaction + */ + @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true, timeout = 30) + public long countActiveBeneficiaries() { + try { + return mappingRepo.countActiveBeneficiaries(); + } catch (Exception e) { + logger.error("Error counting beneficiaries: {}", e.getMessage()); + throw e; + } + } + + /** + * Check if beneficiary exists in a new transaction + */ + @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true, timeout = 10) + public boolean existsByBenRegId(BigInteger benRegId) { + try { + return mappingRepo.existsByBenRegId(benRegId); + } catch (Exception e) { + logger.error("Error checking existence for benRegId={}: {}", benRegId, e.getMessage()); + throw e; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchIndexingService.java b/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchIndexingService.java new file mode 100644 index 00000000..10545bdb --- /dev/null +++ b/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchIndexingService.java @@ -0,0 +1,290 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.service.elasticsearch; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.mapping.*; +import co.elastic.clients.elasticsearch.indices.IndexSettings; +import co.elastic.clients.elasticsearch.indices.TranslogDurability; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +@Service +public class ElasticsearchIndexingService { + + private static final Logger logger = LoggerFactory.getLogger(ElasticsearchIndexingService.class); + + @Autowired + private ElasticsearchClient esClient; + + @Autowired + private ElasticsearchSyncService syncService; + + @Value("${elasticsearch.index.beneficiary}") + private String beneficiaryIndex; + + /** + * Create index optimized for BULK INDEXING + * Settings will be updated for search after sync completes + */ + public void createIndexWithMapping() throws Exception { + logger.info("Creating index optimized for bulk indexing: {}", beneficiaryIndex); + + // Delete existing index if it exists + if (esClient.indices().exists(e -> e.index(beneficiaryIndex)).value()) { + logger.warn("Index {} already exists, deleting...", beneficiaryIndex); + esClient.indices().delete(d -> d.index(beneficiaryIndex)); + } + + // PHASE 1 SETTINGS: Optimized for BULK INDEXING (maximum write speed) + IndexSettings settings = IndexSettings.of(s -> s + // CRITICAL: Disable refresh during bulk indexing + .refreshInterval(t -> t.time("-1")) // -1 = disable completely for max speed + + // Use 1 shard for datasets < 50GB (optimal for 784K records) + .numberOfShards("1") + + // No replicas during initial indexing (add later for HA) + .numberOfReplicas("0") + + // Disable query cache during indexing + .queries(q -> q + .cache(c -> c.enabled(false))) + + .maxResultWindow(10000) + + // CRITICAL: Async translog for maximum speed + .translog(t -> t + .durability(TranslogDurability.Async) + .syncInterval(ts -> ts.time("120s")) // Longer interval for bulk ops + )); + + // Field mappings (supports fast search) + TypeMapping mapping = TypeMapping.of(tm -> tm + .properties("benId", Property.of(p -> p.keyword(k -> k))) + .properties("benRegId", Property.of(p -> p.long_(l -> l))) + .properties("beneficiaryID", Property.of(p -> p.keyword(k -> k))) + + .properties("firstName", Property.of(p -> p.text(t -> t + .analyzer("standard") + .fields("keyword", Property.of(fp -> fp.keyword(k -> k.ignoreAbove(256)))) + .fields("prefix", Property.of(fp -> fp.text(txt -> txt + .analyzer("standard") + .indexPrefixes(ip -> ip.minChars(2).maxChars(5)))))))) + + .properties("middleName", Property.of(p -> p.text(t -> t + .analyzer("standard") + .fields("keyword", Property.of(fp -> fp.keyword(k -> k.ignoreAbove(256)))) + .fields("prefix", Property.of(fp -> fp.text(txt -> txt + .analyzer("standard") + .indexPrefixes(ip -> ip.minChars(2).maxChars(5)))))))) + + .properties("lastName", Property.of(p -> p.text(t -> t + .analyzer("standard") + .fields("keyword", Property.of(fp -> fp.keyword(k -> k.ignoreAbove(256)))) + .fields("prefix", Property.of(fp -> fp.text(txt -> txt + .analyzer("standard") + .indexPrefixes(ip -> ip.minChars(2).maxChars(5)))))))) + + .properties("fatherName", Property.of(p -> p.text(t -> t + .analyzer("standard") + .fields("keyword", Property.of(fp -> fp.keyword(k -> k.ignoreAbove(256))))))) + + .properties("spouseName", Property.of(p -> p.text(t -> t + .analyzer("standard") + .fields("keyword", Property.of(fp -> fp.keyword(k -> k.ignoreAbove(256))))))) + + .properties("maritalStatusID", Property.of(p -> p.integer(i -> i))) + .properties("maritalStatusName", Property.of(p -> p.keyword(k -> k))) + + .properties("genderID", Property.of(p -> p.integer(i -> i))) + .properties("genderName", Property.of(p -> p.keyword(k -> k))) + .properties("dOB", Property.of(p -> p.date(d -> d.format("strict_date_optional_time||epoch_millis")))) + .properties("age", Property.of(p -> p.integer(i -> i))) + + .properties("phoneNum", Property.of(p -> p.keyword(k -> k + .fields("ngram", Property.of(fp -> fp.text(txt -> txt + .analyzer("standard") + .searchAnalyzer("standard"))))))) + + .properties("isHIVPos", Property.of(p -> p.keyword(k -> k))) + .properties("createdBy", Property.of(p -> p.keyword(k -> k))) + .properties("createdDate", Property.of(p -> p.date(d -> d))) + .properties("lastModDate", Property.of(p -> p.long_(l -> l))) + .properties("benAccountID", Property.of(p -> p.long_(l -> l))) + + .properties("healthID", Property.of(p -> p.keyword(k -> k))) + .properties("abhaID", Property.of(p -> p.keyword(k -> k))) + .properties("abhaCreatedDate", Property.of(p -> p.keyword(k -> k))) + .properties("familyID", Property.of(p -> p.keyword(k -> k))) + + // Geographic fields + .properties("stateID", Property.of(p -> p.integer(i -> i))) + .properties("stateName", Property.of(p -> p.keyword(k -> k))) + .properties("districtID", Property.of(p -> p.integer(i -> i))) + .properties("districtName", Property.of(p -> p.keyword(k -> k))) + .properties("blockID", Property.of(p -> p.integer(i -> i))) + .properties("blockName", Property.of(p -> p.keyword(k -> k))) + .properties("villageID", Property.of(p -> p.integer(i -> i))) + .properties("villageName", Property.of(p -> p.keyword(k -> k))) + .properties("pinCode", Property.of(p -> p.keyword(k -> k))) + .properties("servicePointID", Property.of(p -> p.integer(i -> i))) + .properties("servicePointName", Property.of(p -> p.keyword(k -> k))) + .properties("parkingPlaceID", Property.of(p -> p.integer(i -> i))) + + // Permanent address fields + .properties("permStateID", Property.of(p -> p.integer(i -> i))) + .properties("permStateName", Property.of(p -> p.keyword(k -> k))) + .properties("permDistrictID", Property.of(p -> p.integer(i -> i))) + .properties("permDistrictName", Property.of(p -> p.keyword(k -> k))) + .properties("permBlockID", Property.of(p -> p.integer(i -> i))) + .properties("permBlockName", Property.of(p -> p.keyword(k -> k))) + .properties("permVillageID", Property.of(p -> p.integer(i -> i))) + .properties("permVillageName", Property.of(p -> p.keyword(k -> k))) + + // Identity fields + .properties("aadharNo", Property.of(p -> p.keyword(k -> k))) + .properties("govtIdentityNo", Property.of(p -> p.keyword(k -> k)))); + + esClient.indices().create(c -> c + .index(beneficiaryIndex) + .settings(settings) + .mappings(mapping)); + + logger.info("Index created with BULK INDEXING optimization"); + logger.info("Settings: refresh=disabled, replicas=0, async_translog, 1 shard"); + } + + /** + * PHASE 2: Optimize for SEARCH after bulk indexing completes + * Call this AFTER indexAllBeneficiaries() finishes + */ + public void optimizeForSearch() throws Exception { + logger.info("PHASE 2: Optimizing index for SEARCH performance"); + + // Step 1: Force refresh to make all documents searchable + logger.info("Step 1/3: Forcing refresh to make documents visible..."); + esClient.indices().refresh(r -> r.index(beneficiaryIndex)); + logger.info("Documents are now searchable"); + + // Step 2: Update settings for production search + logger.info("Step 2/3: Updating index settings for production..."); + esClient.indices().putSettings(s -> s + .index(beneficiaryIndex) + .settings(is -> is + .refreshInterval(t -> t.time("1s")) // Enable 1s refresh for near real-time search + .numberOfReplicas("1") // Add replica for high availability + .translog(t -> t + .durability(TranslogDurability.Request) // Synchronous for data safety + .syncInterval(ts -> ts.time("5s"))) + .queries(q -> q + .cache(c -> c.enabled(true)) // Enable query cache for faster searches + ))); + logger.info("Settings applied: refresh=1s, replicas=1, query_cache=enabled"); + + // Step 3: Force merge to optimize segments + logger.info("Step 3/3: Force merging segments for optimal read performance..."); + logger.info("This may take 5-15 minutes depending on data size..."); + esClient.indices().forcemerge(f -> f + .index(beneficiaryIndex) + .maxNumSegments(1L) // Single segment per shard = fastest searches + .flush(true)); + logger.info("Segments merged to 1 per shard"); + + logger.info("INDEX OPTIMIZATION COMPLETE!"); + logger.info("Index is now ready for searches"); + } + + /** + * COMPLETE WORKFLOW: Create index + Sync data + Optimize + */ + public Map indexAllBeneficiaries() { + logger.info("COMPLETE INDEXING WORKFLOW"); + + long startTime = System.currentTimeMillis(); + + try { + // Execute bulk indexing (now uses optimized batch queries) + logger.info("PHASE 1: Bulk indexing beneficiaries with batch queries..."); + ElasticsearchSyncService.SyncResult result = syncService.syncAllBeneficiaries(); + + long indexingTime = System.currentTimeMillis() - startTime; + logger.info("Bulk indexing completed in {} seconds ({} minutes)", + indexingTime / 1000, indexingTime / 60000); + logger.info("Success: {}, Failed: {}", result.getSuccessCount(), result.getFailureCount()); + + // Optimize for search + logger.info(""); + logger.info("PHASE 2: Optimizing for search..."); + long optimizeStart = System.currentTimeMillis(); + optimizeForSearch(); + long optimizeTime = System.currentTimeMillis() - optimizeStart; + logger.info("Optimization completed in {} seconds ({} minutes)", + optimizeTime / 1000, optimizeTime / 60000); + + long totalTime = System.currentTimeMillis() - startTime; + + logger.info("COMPLETE WORKFLOW FINISHED!"); + logger.info("Total time: {} seconds ({} minutes)", totalTime / 1000, totalTime / 60000); + logger.info("Indexing: {}m | Optimization: {}m", indexingTime / 60000, optimizeTime / 60000); + + // Return response in your existing format + Map response = new HashMap<>(); + response.put("success", result.getSuccessCount()); + response.put("failed", result.getFailureCount()); + + return response; + + } catch (Exception e) { + logger.error("Error during indexing workflow", e); + Map response = new HashMap<>(); + response.put("success", 0); + response.put("failed", 0); + return response; + } + } + + /** + * Get index statistics + */ + public Map getIndexStats() throws Exception { + var stats = esClient.indices().stats(s -> s.index(beneficiaryIndex)); + var settings = esClient.indices().getSettings(g -> g.index(beneficiaryIndex)); + + Map info = new HashMap<>(); + info.put("documentCount", stats.indices().get(beneficiaryIndex).primaries().docs().count()); + info.put("sizeInBytes", stats.indices().get(beneficiaryIndex).primaries().store().sizeInBytes()); + info.put("refreshInterval", settings.get(beneficiaryIndex).settings().index().refreshInterval().time()); + info.put("numberOfShards", settings.get(beneficiaryIndex).settings().index().numberOfShards()); + info.put("numberOfReplicas", settings.get(beneficiaryIndex).settings().index().numberOfReplicas()); + + return info; + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchService.java b/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchService.java new file mode 100644 index 00000000..2fabc1ec --- /dev/null +++ b/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchService.java @@ -0,0 +1,1093 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.service.elasticsearch; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.query_dsl.FunctionBoostMode; +import co.elastic.clients.elasticsearch._types.query_dsl.FunctionScore; +import co.elastic.clients.elasticsearch._types.query_dsl.FunctionScoreMode; +import co.elastic.clients.elasticsearch.core.SearchResponse; + +import com.iemr.common.identity.dto.BeneficiariesESDTO; +import com.iemr.common.identity.repo.BenDetailRepo; +import com.iemr.common.identity.repo.V_BenAdvanceSearchRepo; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; + +import co.elastic.clients.elasticsearch._types.SortOrder; +import com.iemr.common.identity.repo.BenAddressRepo; + +@Service +public class ElasticsearchService { + + private static final Logger logger = LoggerFactory.getLogger(ElasticsearchService.class); + + @Autowired + private ElasticsearchClient esClient; + + @Autowired + private BenDetailRepo benDetailRepo; + + @Autowired + private BenAddressRepo benAddressRepo; + + @Autowired + private V_BenAdvanceSearchRepo v_BenAdvanceSearchRepo; + + @Value("${elasticsearch.index.beneficiary}") + private String beneficiaryIndex; + + @Value("${elasticsearch.enabled}") + private boolean esEnabled; + + /** + * Universal search with score-based filtering and location ranking + * Only returns records that actually match the query (not all 10000) + */ + public List> universalSearch(String query, Integer userId) { + try { + final Map userLocation = (userId != null) ? getUserLocation(userId) : null; + + boolean isNumeric = query.matches("\\d+"); + double minScore = isNumeric ? 1.0 : 1.5; + + SearchResponse response = esClient.search(s -> s + .index(beneficiaryIndex) + .preference("_local") + + .requestCache(true) + + .query(q -> q + .functionScore(fs -> fs + .query(qq -> qq + .bool(b -> { + if (!isNumeric) { + String[] queryWords = query.trim().split("\\s+"); + boolean isMultiWord = queryWords.length > 1; + + if (isMultiWord) { + // MULTI-WORD SEARCH: Each word should match at least one name field + // Using should instead of must so that partial matches still return results + // e.g., "Ravi Kumar XYZ" → if only "Ravi" and "Kumar" match, results are still returned + for (String word : queryWords) { + final String w = word; + final String wLower = word.toLowerCase(); + b.should(m -> m.bool(wb -> { + // Exact match (case-insensitive via lowercase) + wb.should(s1 -> s1.term(t -> t.field("firstName.keyword").value(wLower).boost(20.0f))); + wb.should(s2 -> s2.term(t -> t.field("middleName.keyword").value(wLower).boost(20.0f))); + wb.should(s3 -> s3.term(t -> t.field("lastName.keyword").value(wLower).boost(20.0f))); + // Prefix match on analyzed text fields (works from 1st character, case-insensitive via analyzer) + wb.should(s4 -> s4.prefix(p -> p.field("firstName").value(wLower).boost(12.0f))); + wb.should(s5 -> s5.prefix(p -> p.field("middleName").value(wLower).boost(12.0f))); + wb.should(s6 -> s6.prefix(p -> p.field("lastName").value(wLower).boost(12.0f))); + // Prefix match on keyword fields (case-insensitive) + wb.should(s7 -> s7.prefix(p -> p.field("firstName.keyword").value(wLower).caseInsensitive(true).boost(10.0f))); + wb.should(s8 -> s8.prefix(p -> p.field("middleName.keyword").value(wLower).caseInsensitive(true).boost(10.0f))); + wb.should(s9 -> s9.prefix(p -> p.field("lastName.keyword").value(wLower).caseInsensitive(true).boost(10.0f))); + // Fuzzy match (kicks in for 3+ char words) + if (w.length() >= 3) { + wb.should(s10 -> s10.match(mm -> mm.field("firstName").query(w).fuzziness("AUTO").prefixLength(1).maxExpansions(50).boost(5.0f))); + wb.should(s11 -> s11.match(mm -> mm.field("middleName").query(w).fuzziness("AUTO").prefixLength(1).maxExpansions(50).boost(5.0f))); + wb.should(s12 -> s12.match(mm -> mm.field("lastName").query(w).fuzziness("AUTO").prefixLength(1).maxExpansions(50).boost(5.0f))); + } + // Wildcard starts-with match (case-insensitive) + if (w.length() >= 2) { + wb.should(s13 -> s13.wildcard(wc -> wc.field("firstName.keyword").value(wLower + "*").caseInsensitive(true).boost(8.0f))); + wb.should(s14 -> s14.wildcard(wc -> wc.field("middleName.keyword").value(wLower + "*").caseInsensitive(true).boost(8.0f))); + wb.should(s15 -> s15.wildcard(wc -> wc.field("lastName.keyword").value(wLower + "*").caseInsensitive(true).boost(8.0f))); + } + // Contains match (case-insensitive) + if (w.length() >= 3) { + wb.should(s16 -> s16.wildcard(wc -> wc.field("firstName.keyword").value("*" + wLower + "*").caseInsensitive(true).boost(3.0f))); + wb.should(s17 -> s17.wildcard(wc -> wc.field("middleName.keyword").value("*" + wLower + "*").caseInsensitive(true).boost(3.0f))); + wb.should(s18 -> s18.wildcard(wc -> wc.field("lastName.keyword").value("*" + wLower + "*").caseInsensitive(true).boost(3.0f))); + } + wb.minimumShouldMatch("1"); + return wb; + })); + } + } else { + // SINGLE-WORD SEARCH: original logic + String queryLower = query.toLowerCase(); + + // 1. EXACT MATCH (highest priority) + b.should(s1 -> s1.term(t -> t + .field("firstName.keyword") + .value(queryLower) + .boost(20.0f))); + b.should(s2 -> s2.term(t -> t + .field("middleName.keyword") + .value(queryLower) + .boost(20.0f))); + b.should(s3 -> s3.term(t -> t + .field("lastName.keyword") + .value(queryLower) + .boost(20.0f))); + + // 2. PREFIX MATCH (high priority for "vani" → "vanitha") + b.should(s4 -> s4.prefix(p -> p + .field("firstName.keyword") + .value(queryLower) + .caseInsensitive(true) + .boost(10.0f))); + b.should(s5 -> s5.prefix(p -> p + .field("middleName.keyword") + .value(queryLower) + .caseInsensitive(true) + .boost(10.0f))); + b.should(s6 -> s6.prefix(p -> p + .field("lastName.keyword") + .value(queryLower) + .caseInsensitive(true) + .boost(10.0f))); + + // 3. FUZZY MATCH (for typos: "vanit" → "vanitha") + // AUTO fuzziness: 1 edit for 3-5 chars, 2 edits for 6+ chars + b.should(s7 -> s7.match(m -> m + .field("firstName") + .query(query) + .fuzziness("AUTO") + .prefixLength(1) + .maxExpansions(50) + .boost(5.0f))); + b.should(s8 -> s8.match(m -> m + .field("middleName") + .query(query) + .fuzziness("AUTO") + .prefixLength(1) + .maxExpansions(50) + .boost(5.0f))); + b.should(s9 -> s9.match(m -> m + .field("lastName") + .query(query) + .fuzziness("AUTO") + .prefixLength(1) + .maxExpansions(50) + .boost(5.0f))); + + // 4. WILDCARD MATCH (for "sur*" → "suraj", "surya") + if (query.length() >= 2) { + b.should(s10 -> s10.wildcard(w -> w + .field("firstName.keyword") + .value(queryLower + "*") + .caseInsensitive(true) + .boost(8.0f))); + b.should(s11 -> s11.wildcard(w -> w + .field("middleName.keyword") + .value(queryLower + "*") + .caseInsensitive(true) + .boost(8.0f))); + b.should(s12 -> s12.wildcard(w -> w + .field("lastName.keyword") + .value(queryLower + "*") + .caseInsensitive(true) + .boost(8.0f))); + } + + // 5. CONTAINS MATCH (for partial matches anywhere) + if (query.length() >= 3) { + b.should(s13 -> s13.wildcard(w -> w + .field("firstName.keyword") + .value("*" + queryLower + "*") + .caseInsensitive(true) + .boost(3.0f))); + b.should(s14 -> s14.wildcard(w -> w + .field("middleName.keyword") + .value("*" + queryLower + "*") + .caseInsensitive(true) + .boost(3.0f))); + b.should(s15 -> s15.wildcard(w -> w + .field("lastName.keyword") + .value("*" + queryLower + "*") + .caseInsensitive(true) + .boost(3.0f))); + } + } + } + + // ID SEARCHES + b.should(s16 -> s16 + .term(t -> t.field("healthID").value(query).boost(25.0f))); + b.should(s17 -> s17 + .term(t -> t.field("abhaID").value(query).boost(25.0f))); + b.should(s18 -> s18 + .term(t -> t.field("beneficiaryID").value(query).boost(25.0f))); + b.should(s19 -> s19 + .term(t -> t.field("benId").value(query).boost(25.0f))); + b.should(s20 -> s20 + .term(t -> t.field("aadharNo").value(query).boost(20.0f))); + + if (isNumeric) { + // PREFIX for phone/IDs + b.should(s21 -> s21 + .prefix(p -> p.field("phoneNum").value(query).boost(8.0f))); + b.should(s22 -> s22 + .prefix(p -> p.field("healthID").value(query).boost(6.0f))); + b.should(s23 -> s23 + .prefix(p -> p.field("abhaID").value(query).boost(6.0f))); + b.should(s24 -> s24.prefix( + p -> p.field("beneficiaryID").value(query).boost(6.0f))); + + // WILDCARD for phone contains + if (query.length() >= 4) { + b.should(s25 -> s25.wildcard(w -> w + .field("phoneNum") + .value("*" + query + "*") + .boost(3.0f))); + } + + // Numeric ID matches + try { + Long numericValue = Long.parseLong(query); + b.should(s26 -> s26.term(t -> t.field("benRegId") + .value(numericValue).boost(25.0f))); + b.should(s27 -> s27.term(t -> t.field("benAccountID") + .value(numericValue).boost(15.0f))); + + // Location matching (if user location available) + int intValue = numericValue.intValue(); + if (userLocation != null) { + Integer userVillageId = userLocation.get("villageId"); + Integer userBlockId = userLocation.get("blockId"); + + if (userVillageId != null && userVillageId == intValue) { + b.should(s28 -> s28.term(t -> t.field("villageID") + .value(intValue).boost(5.0f))); + } + if (userBlockId != null && userBlockId == intValue) { + b.should(s29 -> s29.term(t -> t.field("blockID") + .value(intValue).boost(3.0f))); + } + } + } catch (NumberFormatException e) { + } + } + + // At least one should clause must match for all query types + b.minimumShouldMatch("1"); + return b; + })) + .functions(getFunctionScores(userLocation)) + .scoreMode(FunctionScoreMode.Sum) + .boostMode(FunctionBoostMode.Multiply) + .maxBoost(5.0))) + .minScore(minScore) + + .size(100) // Reduced from 500 + + .sort(so -> so.score(sc -> sc.order(SortOrder.Desc))) + + .source(src -> src + .filter(f -> f + .includes("benRegId", "beneficiaryID", "firstName", "middleName", "lastName", + "genderID", "genderName", "dOB", "phoneNum", + "stateID", "stateName", "districtID", "blockID", "villageID", "healthID", + "abhaID", + "abhaCreatedDate", + "familyID", + "fatherName", "spouseName","maritalStatusID", "maritalStatusName", "age", "createdBy", "createdDate", + "lastModDate", "benAccountID", "districtName", "blockName", + "villageName", "pinCode", "servicePointID", "servicePointName", + "parkingPlaceID", "permStateID", "permStateName", "permDistrictID", + "permDistrictName", "permBlockID", "permBlockName", "permVillageID", + "permVillageName"))) + + , BeneficiariesESDTO.class); + + logger.info("ES returned {} hits in {}ms for query: '{}'", + response.hits().hits().size(), + response.took(), + query); + + if (!response.hits().hits().isEmpty()) { + BeneficiariesESDTO firstResult = response.hits().hits().get(0).source(); + logger.info("Top result - score: {}, benRegId: {}, name: {} {}", + response.hits().hits().get(0).score(), + firstResult.getBenRegId(), + firstResult.getFirstName(), + firstResult.getLastName()); + } + + if (response.hits().hits().isEmpty()) { + logger.info("No results in ES, using database fallback"); + return searchInDatabaseDirectly(query); + } + + List> results = response.hits().hits().stream() + .map(hit -> { + Map result = mapESResultToExpectedFormat(hit.source()); + if (result != null) { + result.put("_score", hit.score()); + } + return result; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + logger.info("Returning {} results", results.size()); + return results; + + } catch (Exception e) { + logger.error("ES search failed: {}", e.getMessage()); + return searchInDatabaseDirectly(query); + } + } + + /** + * Generate function scores for location-based ranking + */ + private List getFunctionScores(Map userLocation) { + if (userLocation == null) { + return List.of(); + } + + List scores = new ArrayList<>(); + + Integer userVillageId = userLocation.get("villageId"); + Integer userBlockId = userLocation.get("blockId"); + + // Village match + if (userVillageId != null) { + scores.add(FunctionScore.of(f -> f + .filter(ff -> ff.term(t -> t.field("villageID").value(userVillageId))) + .weight(2.0))); + } + + // Block match + if (userBlockId != null) { + scores.add(FunctionScore.of(f -> f + .filter(ff -> ff.term(t -> t.field("blockID").value(userBlockId))) + .weight(1.5))); + } + + return scores; + } + + /** + * Advanced search with filter context + */ + public List> advancedSearch( + String firstName, + String middleName, + String lastName, + Integer genderId, + Date dob, + Integer stateId, + Integer districtId, + Integer blockId, + Integer villageId, + String fatherName, + String spouseName, + String maritalStatus, + String phoneNumber, + String beneficiaryId, + String healthId, + String aadharNo, + Integer userId) { + + try { + final Map userLocation = (userId != null) ? getUserLocation(userId) : null; + + SearchResponse response = esClient.search(s -> s + .index(beneficiaryIndex) + .preference("_local") + .requestCache(true) + + .query(q -> q + .bool(b -> { + // Use FILTER context for exact matches (faster, cached) + if (genderId != null) { + b.filter(f -> f.term(t -> t.field("genderID").value(genderId))); + } + if (stateId != null) { + b.filter(f -> f.term(t -> t.field("stateID").value(stateId))); + } + if (districtId != null) { + b.filter(f -> f.term(t -> t.field("districtID").value(districtId))); + } + if (blockId != null) { + b.filter(f -> f.term(t -> t.field("blockID").value(blockId))); + } + if (villageId != null) { + b.filter(f -> f.term(t -> t.field("villageID").value(villageId))); + } + + // MUST context for scored searches + if (firstName != null && !firstName.trim().isEmpty()) { + b.must(m -> m.bool(bb -> bb + .should(s1 -> s1.term( + t -> t.field("firstName.keyword").value(firstName).boost(5.0f))) + .should(s2 -> s2 + .match(mm -> mm.field("firstName").query(firstName).boost(2.0f))) + .minimumShouldMatch("1"))); + } + if (middleName != null && !middleName.trim().isEmpty()) { + b.must(m -> m.bool(bb -> bb + .should(s1 -> s1.term( + t -> t.field("middleName.keyword").value(middleName).boost(5.0f))) + .should(s2 -> s2 + .match(mm -> mm.field("middleName").query(middleName).boost(2.0f))) + .minimumShouldMatch("1"))); + } + + if (lastName != null && !lastName.trim().isEmpty()) { + b.must(m -> m.bool(bb -> bb + .should(s1 -> s1 + .term(t -> t.field("lastName.keyword").value(lastName).boost(5.0f))) + .should(s2 -> s2 + .match(mm -> mm.field("lastName").query(lastName).boost(2.0f))) + .minimumShouldMatch("1"))); + } + + // Exact match IDs in filter context + if (beneficiaryId != null && !beneficiaryId.trim().isEmpty()) { + b.filter(f -> f.term(t -> t.field("beneficiaryID").value(beneficiaryId))); + } + if (healthId != null && !healthId.trim().isEmpty()) { + b.filter(f -> f.bool(bb -> bb + .should(s1 -> s1.term(t -> t.field("healthID").value(healthId))) + .should(s2 -> s2.term(t -> t.field("abhaID").value(healthId))) + .minimumShouldMatch("1"))); + } + if (aadharNo != null && !aadharNo.trim().isEmpty()) { + b.filter(f -> f.term(t -> t.field("aadharNo").value(aadharNo))); + } + if (phoneNumber != null && !phoneNumber.trim().isEmpty()) { + b.must(m -> m.bool(bb -> bb + .should(s1 -> s1 + .term(t -> t.field("phoneNum").value(phoneNumber).boost(3.0f))) + .should(s2 -> s2 + .prefix(p -> p.field("phoneNum").value(phoneNumber).boost(2.0f))) + .minimumShouldMatch("1"))); + } + + return b; + })) + .size(100) + .sort(so -> so.score(sc -> sc.order(SortOrder.Desc))) + + , BeneficiariesESDTO.class); + + if (response.hits().hits().isEmpty()) { + return searchInDatabaseForAdvanced(firstName, middleName, lastName, genderId, dob, + stateId, districtId, blockId, villageId, fatherName, spouseName, maritalStatus, + phoneNumber, beneficiaryId, healthId, aadharNo); + } + + return response.hits().hits().stream() + .map(hit -> mapESResultToExpectedFormat(hit.source())) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + } catch (Exception e) { + logger.error("ES advanced search failed: {}", e.getMessage()); + return searchInDatabaseForAdvanced(firstName, middleName, lastName, genderId, dob, + stateId, districtId, blockId, villageId, fatherName, spouseName, maritalStatus, + phoneNumber, beneficiaryId, healthId, aadharNo); + } + } + + /** + * Database fallback for advanced search + */ + private List> searchInDatabaseForAdvanced( + String firstName, String middleName, String lastName, Integer genderId, Date dob, + Integer stateId, Integer districtId, Integer blockId, Integer villageId, + String fatherName, String spouseName, String maritalStatus, + String phoneNumber, String beneficiaryId, String healthId, String aadharNo) { + + try { + List results = benDetailRepo.advancedSearchBeneficiaries( + firstName, middleName, lastName, genderId, dob, stateId, districtId, + blockId, fatherName, spouseName, maritalStatus, phoneNumber, + beneficiaryId); + + return results.stream() + .map(this::mapToExpectedFormat) + .collect(Collectors.toList()); + + } catch (Exception e) { + logger.error("Database advanced search failed: {}", e.getMessage(), e); + return Collections.emptyList(); + } + } + + /** + * Overloaded method without userId (backward compatibility) + */ + public List> universalSearch(String query) { + return universalSearch(query, null); + } + + /** + * Get user location from database + */ + private Map getUserLocation(Integer userId) { + try { + List results = benAddressRepo.getUserLocation(userId); + if (results != null && !results.isEmpty()) { + Object[] row = results.get(0); + Map location = new HashMap<>(); + location.put("psmId", getInteger(row[0])); + location.put("blockId", getInteger(row[1])); + location.put("villageId", getInteger(row[2])); + location.put("servicePointId", getInteger(row[3])); + return location; + } + } catch (Exception e) { + logger.error("Error fetching user location: {}", e.getMessage(), e); + } + return null; + } + + /** + * Calculate location match score + * Higher score = better match + */ + private int calculateLocationScore(Map beneficiary, + Integer userBlockId, + Integer userVillageId) { + int score = 0; + + try { + Map demographics = (Map) beneficiary.get("i_bendemographics"); + if (demographics == null) { + return score; + } + + Integer currBlockId = getIntegerFromMap(demographics, "blockID"); + Integer currVillageId = getIntegerFromMap(demographics, "m_districtblock", "blockID"); + + // Village match (highest priority) - score: 100 + if (userVillageId != null && userVillageId.equals(currVillageId)) { + score += 100; + } + + // Block match - score: 50 + if (userBlockId != null && userBlockId.equals(currBlockId)) { + score += 50; + } + + Integer permBlockId = getIntegerFromMap(beneficiary, "permBlockID"); + Integer permVillageId = getIntegerFromMap(beneficiary, "permVillageID"); + + if (userVillageId != null && userVillageId.equals(permVillageId)) { + score += 75; + } + + if (userBlockId != null && userBlockId.equals(permBlockId)) { + score += 25; + } + + } catch (Exception e) { + logger.error("Error calculating location score: {}", e.getMessage()); + } + + return score; + } + + /** + * Helper to safely get Integer from nested maps + */ + private Integer getIntegerFromMap(Map map, String... keys) { + Object value = map; + for (String key : keys) { + if (value instanceof Map) { + value = ((Map) value).get(key); + } else { + return null; + } + } + return value instanceof Integer ? (Integer) value : null; + } + + /** + * Map ES DTO directly to expected API format with COMPLETE data + */ + private Map mapESResultToExpectedFormat(BeneficiariesESDTO esData) { + if (esData == null) { + return null; + } + + Map result = new HashMap<>(); + + try { + // Basic fields from ES + result.put("beneficiaryRegID", esData.getBenRegId()); + result.put("beneficiaryID", esData.getBeneficiaryID()); + result.put("firstName", esData.getFirstName()); + result.put("middleName", esData.getMiddleName() != null ? esData.getMiddleName() : ""); + result.put("lastName", esData.getLastName() != null ? esData.getLastName() : ""); + result.put("genderID", esData.getGenderID()); + result.put("genderName", esData.getGenderName()); + result.put("dob", esData.getDOB()); + result.put("dOB", esData.getDOB()); + result.put("age", esData.getAge()); + result.put("actualAge", esData.getAge()); + result.put("ageUnits", "Years"); + + result.put("fatherName", esData.getFatherName() != null ? esData.getFatherName() : ""); + result.put("spouseName", esData.getSpouseName() != null ? esData.getSpouseName() : ""); + result.put("maritalStatusID", esData.getMaritalStatusID() != null ? esData.getMaritalStatusID() : ""); + result.put("maritalStatusName", esData.getMaritalStatusName() != null ? esData.getMaritalStatusName() : ""); + result.put("isHIVPos", esData.getIsHIVPos() != null ? esData.getIsHIVPos() : ""); + + result.put("createdBy", esData.getCreatedBy()); + result.put("createdDate", esData.getCreatedDate()); + result.put("lastModDate", esData.getLastModDate()); + result.put("benAccountID", esData.getBenAccountID()); + + // Family ID - use both formats + result.put("familyID", esData.getFamilyID()); + result.put("familyId", esData.getFamilyID()); + + // Head of family - NEW (add these fields to BeneficiariesESDTO if available) + result.put("headOfFamily_RelationID", null); + result.put("headOfFamily_Relation", null); + + // ABHA Details + List> abhaDetails = new ArrayList<>(); + if (esData.getHealthID() != null || esData.getAbhaID() != null) { + Map abhaDetail = new HashMap<>(); + abhaDetail.put("healthIDNumber", esData.getAbhaID()); + abhaDetail.put("healthID", esData.getAbhaID()); + if (esData.getAbhaCreatedDate() != null) { + + String dateStr = esData.getAbhaCreatedDate(); + ZoneId zoneId = ZoneId.of("Asia/Kolkata"); + long createdDateMillis; + + if (dateStr.length() == 10) { + // yyyy-MM-dd + createdDateMillis = LocalDate.parse(dateStr) + .atStartOfDay(zoneId) + .toInstant() + .toEpochMilli(); + } else { + // yyyy-MM-dd HH:mm:ss OR yyyy-MM-dd HH:mm:ss.S + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss[.S]"); + + createdDateMillis = LocalDateTime.parse(dateStr, formatter) + .atZone(zoneId) + .toInstant() + .toEpochMilli(); + } + + abhaDetail.put("createdDate", createdDateMillis); + } + abhaDetail.put("beneficiaryRegID", esData.getBenRegId()); + abhaDetails.add(abhaDetail); + } + else + { + try { + List abhaRecords = v_BenAdvanceSearchRepo.getBenAbhaDetailsByBenRegID(BigInteger.valueOf(esData.getBenRegId())); + for (Object[] record : abhaRecords) { + Map abhaDetail = new HashMap<>(); + abhaDetail.put("healthIDNumber", record[0] != null ? record[0].toString() : null); + abhaDetail.put("healthID", record[0] != null ? record[0].toString() : null); + abhaDetail.put("createdDate", record[1] != null ? ((Timestamp) record[1]).getTime() : null); + abhaDetail.put("beneficiaryRegID", esData.getBenRegId()); + abhaDetails.add(abhaDetail); + } + } catch (Exception e) { + logger.warn("ABHA query returned error (likely no records): {}", e.getMessage()); + + } + } + result.put("abhaDetails", abhaDetails); + + // Gender object + Map mGender = new HashMap<>(); + mGender.put("genderID", esData.getGenderID()); + mGender.put("genderName", esData.getGenderName()); + result.put("m_gender", mGender); + + // Demographics - UPDATED with all fields + Map demographics = new HashMap<>(); + demographics.put("beneficiaryRegID", esData.getBenRegId()); + + // Current address + demographics.put("stateID", esData.getStateID()); + demographics.put("stateName", esData.getStateName()); + demographics.put("districtID", esData.getDistrictID()); + demographics.put("districtName", esData.getDistrictName()); + demographics.put("blockID", esData.getBlockID()); + demographics.put("blockName", esData.getBlockName()); + demographics.put("villageID", esData.getVillageID()); // FIXED + demographics.put("villageName", esData.getVillageName()); // FIXED + + demographics.put("districtBranchID", esData.getVillageID()); + demographics.put("districtBranchName", esData.getVillageName()); + demographics.put("parkingPlaceID", esData.getParkingPlaceID()); + demographics.put("servicePointID", esData.getServicePointID()); + demographics.put("servicePointName", esData.getServicePointName()); + demographics.put("createdBy", esData.getCreatedBy()); + + // State object + Map mState = new HashMap<>(); + mState.put("stateID", esData.getStateID()); + mState.put("stateName", esData.getStateName()); + mState.put("stateCode", null); + mState.put("countryID", 1); + demographics.put("m_state", mState); + + // District object + Map mDistrict = new HashMap<>(); + mDistrict.put("districtID", esData.getDistrictID()); + mDistrict.put("districtName", esData.getDistrictName()); + mDistrict.put("stateID", esData.getStateID()); + demographics.put("m_district", mDistrict); + + // Block object + Map mBlock = new HashMap<>(); + mBlock.put("blockID", esData.getBlockID()); + mBlock.put("blockName", esData.getBlockName()); + mBlock.put("districtID", esData.getDistrictID()); + mBlock.put("stateID", esData.getStateID()); + demographics.put("m_districtblock", mBlock); + + // Branch mapping object + Map mBranch = new HashMap<>(); + mBranch.put("districtBranchID", null); + mBranch.put("blockID", esData.getBlockID()); + mBranch.put("villageName", esData.getVillageName()); // FIXED + mBranch.put("pinCode", esData.getPinCode()); + demographics.put("m_districtbranchmapping", mBranch); + + result.put("i_bendemographics", demographics); + + // Phone maps + List> benPhoneMaps = new ArrayList<>(); + if (esData.getPhoneNum() != null && !esData.getPhoneNum().isEmpty()) { + Map phoneMap = new HashMap<>(); + phoneMap.put("benPhMapID", 1L); + phoneMap.put("benificiaryRegID", esData.getBenRegId()); + phoneMap.put("parentBenRegID", esData.getBenRegId()); + phoneMap.put("benRelationshipID", 1); + phoneMap.put("phoneNo", esData.getPhoneNum()); + + Map relationType = new HashMap<>(); + relationType.put("benRelationshipID", 1); + relationType.put("benRelationshipType", "Self"); + phoneMap.put("benRelationshipType", relationType); + + benPhoneMaps.add(phoneMap); + } + result.put("benPhoneMaps", benPhoneMaps); + + // Boolean flags + result.put("isConsent", false); + result.put("changeInSelfDetails", false); + result.put("changeInAddress", false); + result.put("changeInContacts", false); + result.put("changeInIdentities", false); + result.put("changeInOtherDetails", false); + result.put("changeInFamilyDetails", false); + result.put("changeInAssociations", false); + result.put("changeInBankDetails", false); + result.put("changeInBenImage", false); + result.put("is1097", false); + result.put("emergencyRegistration", false); + result.put("passToNurse", false); + + // Empty objects/arrays + result.put("m_title", new HashMap<>()); + result.put("maritalStatus", new HashMap<>()); + result.put("beneficiaryIdentities", new ArrayList<>()); + + } catch (Exception e) { + logger.error("Error mapping ES result: {}", e.getMessage(), e); + return null; + } + + return result; + } + + /** + * Direct database search as fallback + */ + private List> searchInDatabaseDirectly(String query) { + try { + List results = benDetailRepo.searchBeneficiaries(query); + + return results.stream() + .map(this::mapToExpectedFormat) + .collect(Collectors.toList()); + + } catch (Exception e) { + logger.error("Database search failed: {}", e.getMessage(), e); + return Collections.emptyList(); + } + } + + /** + * Map database result to expected API format + */ + private Map mapToExpectedFormat(Object[] row) { + Map result = new HashMap<>(); + + try { + Long beneficiaryRegID = getLong(row[0]); + String beneficiaryID = getString(row[1]); + String firstName = getString(row[2]); + String middleName = getString(row[3]); + String lastName = getString(row[4]); + Integer genderID = getInteger(row[5]); + String genderName = getString(row[6]); + Date dob = getDate(row[7]); + Integer age = getInteger(row[8]); + String fatherName = getString(row[9]); + String spouseName = getString(row[10]); + String maritalStatusID = getString(row[11]); + String maritalStatusName = getString(row[12]); + String isHIVPos = getString(row[13]); + String createdBy = getString(row[14]); + Date createdDate = getDate(row[15]); + Long lastModDate = getLong(row[16]); + Long benAccountID = getLong(row[17]); + + Integer stateID = getInteger(row[18]); + String stateName = getString(row[19]); + Integer districtID = getInteger(row[20]); + String districtName = getString(row[21]); + Integer blockID = getInteger(row[22]); + String blockName = getString(row[23]); + String pinCode = getString(row[24]); + Integer servicePointID = getInteger(row[25]); + String servicePointName = getString(row[26]); + Integer parkingPlaceID = getInteger(row[27]); + String phoneNum = getString(row[28]); + + Integer villageID = getInteger(row[29]); + String villageName = getString(row[30]); + + result.put("beneficiaryRegID", beneficiaryRegID); + result.put("beneficiaryID", beneficiaryID); + result.put("firstName", firstName); + result.put("middleName", middleName != null ? middleName : ""); + result.put("lastName", lastName != null ? lastName : ""); + result.put("genderID", genderID); + result.put("genderName", genderName); + result.put("dOB", dob); + result.put("dob", dob); + result.put("age", age); + result.put("actualAge", age); + result.put("ageUnits", "Years"); + result.put("fatherName", fatherName != null ? fatherName : ""); + result.put("spouseName", spouseName != null ? spouseName : ""); + result.put("maritalStatusID", maritalStatusID != null ? maritalStatusID : ""); + result.put("maritalStatusName", maritalStatusName != null ? maritalStatusName : ""); + result.put("isHIVPos", isHIVPos != null ? isHIVPos : ""); + result.put("createdBy", createdBy); + result.put("createdDate", createdDate); + result.put("lastModDate", lastModDate); + result.put("benAccountID", benAccountID); + + Map mGender = new HashMap<>(); + mGender.put("genderID", genderID); + mGender.put("genderName", genderName); + result.put("m_gender", mGender); + + Map demographics = new HashMap<>(); + demographics.put("beneficiaryRegID", beneficiaryRegID); + demographics.put("stateID", stateID); + demographics.put("stateName", stateName); + demographics.put("districtID", districtID); + demographics.put("districtName", districtName); + demographics.put("blockID", blockID); + demographics.put("blockName", blockName); + demographics.put("villageID", villageID); + demographics.put("villageName", villageName); + demographics.put("districtBranchID", villageID); + demographics.put("districtBranchName", villageName); + demographics.put("parkingPlaceID", parkingPlaceID); + demographics.put("servicePointID", servicePointID); + demographics.put("servicePointName", servicePointName); + demographics.put("createdBy", createdBy); + + Map mState = new HashMap<>(); + mState.put("stateID", stateID); + mState.put("stateName", stateName); + mState.put("stateCode", null); + mState.put("countryID", 1); + demographics.put("m_state", mState); + + Map mDistrict = new HashMap<>(); + mDistrict.put("districtID", districtID); + mDistrict.put("districtName", districtName); + mDistrict.put("stateID", stateID); + demographics.put("m_district", mDistrict); + + Map mBlock = new HashMap<>(); + mBlock.put("blockID", blockID); + mBlock.put("blockName", blockName); + mBlock.put("districtID", districtID); + mBlock.put("stateID", stateID); + demographics.put("m_districtblock", mBlock); + + Map mBranch = new HashMap<>(); + mBranch.put("districtBranchID", null); + mBranch.put("blockID", blockID); + mBranch.put("villageName", villageName); // FIXED + mBranch.put("pinCode", pinCode); + demographics.put("m_districtbranchmapping", mBranch); + + result.put("i_bendemographics", demographics); + + List> benPhoneMaps = fetchPhoneNumbers(beneficiaryRegID); + result.put("benPhoneMaps", benPhoneMaps); + + result.put("isConsent", false); + result.put("m_title", new HashMap<>()); + result.put("maritalStatus", new HashMap<>()); + result.put("changeInSelfDetails", false); + result.put("changeInAddress", false); + result.put("changeInContacts", false); + result.put("changeInIdentities", false); + result.put("changeInOtherDetails", false); + result.put("changeInFamilyDetails", false); + result.put("changeInAssociations", false); + result.put("changeInBankDetails", false); + result.put("changeInBenImage", false); + result.put("is1097", false); + result.put("emergencyRegistration", false); + result.put("passToNurse", false); + result.put("beneficiaryIdentities", new ArrayList<>()); + + } catch (Exception e) { + logger.error("Error mapping result: {}", e.getMessage(), e); + } + + return result; + } + + /** + * Fetch phone numbers for a beneficiary + */ + private List> fetchPhoneNumbers(Long beneficiaryRegID) { + List> phoneList = new ArrayList<>(); + + try { + List phones = benDetailRepo.findPhoneNumbersByBeneficiaryId(beneficiaryRegID); + + int mapId = 1; + for (Object[] phone : phones) { + String phoneNo = getString(phone[0]); + String phoneType = getString(phone[1]); + + if (phoneNo != null && !phoneNo.isEmpty()) { + Map phoneMap = new HashMap<>(); + phoneMap.put("benPhMapID", (long) mapId++); + phoneMap.put("benificiaryRegID", beneficiaryRegID); + phoneMap.put("parentBenRegID", beneficiaryRegID); + phoneMap.put("benRelationshipID", 1); + phoneMap.put("phoneNo", phoneNo); + + Map relationType = new HashMap<>(); + relationType.put("benRelationshipID", 1); + relationType.put("benRelationshipType", phoneType != null ? phoneType : "Self"); + phoneMap.put("benRelationshipType", relationType); + + phoneList.add(phoneMap); + } + } + } catch (Exception e) { + logger.error("Error fetching phone numbers: {}", e.getMessage(), e); + } + + return phoneList; + } + + // Helper methods + private String getString(Object value) { + if (value == null) + return null; + return value.toString(); + } + + private Long getLong(Object value) { + if (value == null) + return null; + if (value instanceof Long) + return (Long) value; + if (value instanceof Integer) + return ((Integer) value).longValue(); + if (value instanceof BigDecimal) + return ((BigDecimal) value).longValue(); + if (value instanceof String) { + try { + return Long.parseLong((String) value); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + private Integer getInteger(Object value) { + if (value == null) + return null; + if (value instanceof Integer) + return (Integer) value; + if (value instanceof Long) + return ((Long) value).intValue(); + if (value instanceof BigDecimal) + return ((BigDecimal) value).intValue(); + if (value instanceof String) { + try { + return Integer.parseInt((String) value); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + private Date getDate(Object value) { + if (value == null) + return null; + if (value instanceof Date) + return (Date) value; + if (value instanceof Timestamp) + return new Date(((Timestamp) value).getTime()); + if (value instanceof java.sql.Date) + return new Date(((java.sql.Date) value).getTime()); + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchSyncService.java b/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchSyncService.java new file mode 100644 index 00000000..9f434b48 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchSyncService.java @@ -0,0 +1,461 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.service.elasticsearch; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.Refresh; +import co.elastic.clients.elasticsearch.core.BulkRequest; +import co.elastic.clients.elasticsearch.core.BulkResponse; +import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem; + +import com.iemr.common.identity.data.elasticsearch.BeneficiaryDocument; + +/** + * Service to synchronize beneficiary data from database to Elasticsearch + */ +@Service +public class ElasticsearchSyncService { + + private static final Logger logger = LoggerFactory.getLogger(ElasticsearchSyncService.class); + + // OPTIMIZED BATCH SIZES for maximum performance + private static final int DB_FETCH_SIZE = 10000; + private static final int ES_BULK_SIZE = 5000; + + @Autowired + private ElasticsearchClient esClient; + + @Autowired + private BeneficiaryTransactionHelper transactionalWrapper; + + @Autowired + private BeneficiaryDocumentDataService documentDataService; // KEY: Batch service with ABHA + + @Value("${elasticsearch.index.beneficiary}") + private String beneficiaryIndex; + + /** + * Sync all beneficiaries using BATCH queries WITH ABHA + * This replaces individual queries with batch fetching (50-100x faster) + */ + public SyncResult syncAllBeneficiaries() { + logger.info("STARTING OPTIMIZED BATCH SYNC WITH ABHA"); + + SyncResult result = new SyncResult(); + long startTime = System.currentTimeMillis(); + + try { + // Get total count + long totalCount = transactionalWrapper.countActiveBeneficiaries(); + logger.info("Total beneficiaries to sync: {}", totalCount); + + if (totalCount == 0) { + logger.warn("No beneficiaries found to sync!"); + return result; + } + + AtomicInteger processedCount = new AtomicInteger(0); + AtomicInteger abhaEnrichedCount = new AtomicInteger(0); + int offset = 0; + List esBatch = new ArrayList<>(ES_BULK_SIZE); + + // Process in large chunks for maximum speed + while (offset < totalCount) { + long chunkStart = System.currentTimeMillis(); + + // STEP 1: Fetch IDs in batch + List batchIds = fetchBatchWithRetry(offset, DB_FETCH_SIZE); + + if (batchIds == null || batchIds.isEmpty()) { + logger.info("No more records to process."); + break; + } + + logger.info("Fetched {} IDs at offset {}", batchIds.size(), offset); + + // STEP 2: Convert IDs to BigInteger list + List benRegIds = new ArrayList<>(batchIds.size()); + for (Object[] idRow : batchIds) { + BigInteger benRegId = convertToBigInteger(idRow[0]); + if (benRegId != null) { + benRegIds.add(benRegId); + } + } + + // STEP 3: BATCH FETCH complete data WITH ABHA (CRITICAL OPTIMIZATION) + // This single call replaces thousands of individual database queries + logger.info("Batch fetching complete data with ABHA for {} beneficiaries...", benRegIds.size()); + List documents = documentDataService.getBeneficiariesBatch(benRegIds); + logger.info("Retrieved {} complete documents", documents.size()); + + // STEP 4: Count ABHA enriched documents and add to ES batch + for (BeneficiaryDocument doc : documents) { + if (doc != null && doc.getBenId() != null) { + + // Track ABHA enrichment + if (doc.getHealthID() != null || doc.getAbhaID() != null) { + abhaEnrichedCount.incrementAndGet(); + logger.debug("Document {} has ABHA: healthID={}, abhaID={}", + doc.getBenId(), doc.getHealthID(), doc.getAbhaID()); + } + + esBatch.add(doc); + + // Bulk index when batch is full + if (esBatch.size() >= ES_BULK_SIZE) { + int indexed = bulkIndexDocuments(esBatch); + result.addSuccess(indexed); + result.addFailure(esBatch.size() - indexed); + + int current = processedCount.addAndGet(esBatch.size()); + logProgress(current, totalCount, abhaEnrichedCount.get(), startTime); + + esBatch.clear(); + } + } else { + result.addFailure(); + } + } + + long chunkTime = System.currentTimeMillis() - chunkStart; + logger.info("Chunk processed in {}ms ({} docs/sec)", + chunkTime, + (batchIds.size() * 1000) / Math.max(chunkTime, 1)); + + offset += DB_FETCH_SIZE; + + // Brief pause to prevent overwhelming the system + Thread.sleep(50); + } + + // Index remaining documents + if (!esBatch.isEmpty()) { + logger.info("Indexing final batch of {} documents...", esBatch.size()); + int indexed = bulkIndexDocuments(esBatch); + result.addSuccess(indexed); + result.addFailure(esBatch.size() - indexed); + processedCount.addAndGet(esBatch.size()); + } + + long totalTime = System.currentTimeMillis() - startTime; + double docsPerSecond = (processedCount.get() * 1000.0) / totalTime; + + logger.info("SYNC COMPLETED SUCCESSFULLY!"); + logger.info("Total Processed: {}", processedCount.get()); + logger.info("Successfully Indexed: {}", result.getSuccessCount()); + logger.info("Failed: {}", result.getFailureCount()); + logger.info("ABHA Enriched: {} ({} %)", + abhaEnrichedCount.get(), + String.format("%.2f", (abhaEnrichedCount.get() * 100.0) / processedCount.get())); + logger.info("Total Time: {} seconds ({} minutes)", totalTime / 1000, totalTime / 60000); + logger.info("Throughput: {:.2f} documents/second", docsPerSecond); + + } catch (Exception e) { + logger.error("CRITICAL ERROR during sync: {}", e.getMessage(), e); + result.setError(e.getMessage()); + } + + return result; + } + + /** + * Fetch batch with automatic retry on connection errors + */ + private List fetchBatchWithRetry(int offset, int limit) { + int maxRetries = 3; + for (int attempt = 1; attempt <= maxRetries; attempt++) { + try { + return transactionalWrapper.getBeneficiaryIdsBatch(offset, limit); + } catch (Exception e) { + logger.warn("Database fetch error (attempt {}/{}): {}", attempt, maxRetries, e.getMessage()); + if (attempt == maxRetries) { + throw new RuntimeException("Failed to fetch batch after " + maxRetries + " attempts", e); + } + try { + Thread.sleep(1000 * attempt); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted during retry", ie); + } + } + } + return null; + } + + /** + * Convert various ID types to BigInteger + */ + private BigInteger convertToBigInteger(Object idObj) { + if (idObj instanceof BigInteger) { + return (BigInteger) idObj; + } else if (idObj instanceof Long) { + return BigInteger.valueOf((Long) idObj); + } else if (idObj instanceof Integer) { + return BigInteger.valueOf((Integer) idObj); + } else { + logger.warn("Unsupported ID type: {}", idObj != null ? idObj.getClass() : "null"); + return null; + } + } + + /** + * Progress logging with ETA and ABHA count + */ + private void logProgress(int current, long total, int abhaCount, long startTime) { + double progress = (current * 100.0) / total; + long elapsed = System.currentTimeMillis() - startTime; + long estimatedTotal = (long) (elapsed / (progress / 100.0)); + long remaining = estimatedTotal - elapsed; + + logger.info("Progress: {}/{} ({:.2f}%) | ABHA: {} | Elapsed: {}m | ETA: {}m | Speed: {:.0f} docs/sec", + current, total, progress, abhaCount, + elapsed / 60000, + remaining / 60000, + (current * 1000.0) / elapsed); + } + + /** + * Bulk index documents to Elasticsearch + */ + private int bulkIndexDocuments(List documents) { + if (documents == null || documents.isEmpty()) { + return 0; + } + + try { + BulkRequest.Builder br = new BulkRequest.Builder(); + + for (BeneficiaryDocument doc : documents) { + if (doc.getBenId() != null) { + br.operations(op -> op + .index(idx -> idx + .index(beneficiaryIndex) + .id(doc.getBenId()) + .document(doc))); + } + } + +// BulkResponse result = esClient.bulk( +// br.refresh(Refresh.WaitFor).build() +// ); + + BulkResponse result = esClient.bulk(br.build()); + + + int successCount = 0; + + if (result.errors()) { + for (BulkResponseItem item : result.items()) { + if (item.error() != null) { + logger.error("Error indexing document {}: {}", item.id(), item.error().reason()); + } else { + successCount++; + } + } + } else { + successCount = documents.size(); + } + + return successCount; + + } catch (Exception e) { + logger.error("Critical error in bulk indexing: {}", e.getMessage(), e); + return 0; + } + } + + /** + * Sync a single beneficiary with ABHA + */ + public boolean syncSingleBeneficiary(String benRegId) { + try { + BigInteger benRegIdBig = new BigInteger(benRegId); + + // Check existence + boolean exists = transactionalWrapper.existsByBenRegId(benRegIdBig); + if (!exists) { + logger.error("Beneficiary does not exist in database: {}", benRegId); + return false; + } + + logger.info("Beneficiary exists in database. Fetching details with ABHA..."); + + // Fetch document using batch service (includes ABHA) + BeneficiaryDocument doc = documentDataService.getBeneficiaryFromDatabase(benRegIdBig); + + if (doc == null || doc.getBenId() == null) { + logger.error("Failed to fetch beneficiary document"); + return false; + } + + logger.info("Document created with ABHA. healthID={}, abhaID={}", + doc.getHealthID(), doc.getAbhaID()); + + // Index to Elasticsearch + esClient.index(i -> i + .index(beneficiaryIndex) + .id(doc.getBenId()) + .document(doc).refresh(Refresh.True)); + + logger.info("SUCCESS! Beneficiary {} synced to Elasticsearch with ABHA", benRegId); + return true; + + } catch (Exception e) { + logger.error("ERROR syncing beneficiary {}: {}", benRegId, e.getMessage(), e); + return false; + } + } + + /** + * Check sync status + */ + public SyncStatus checkSyncStatus() { + try { + long dbCount = transactionalWrapper.countActiveBeneficiaries(); + long esCount = esClient.count(c -> c.index(beneficiaryIndex)).count(); + + SyncStatus status = new SyncStatus(); + status.setDatabaseCount(dbCount); + status.setElasticsearchCount(esCount); + status.setSynced(dbCount == esCount); + status.setMissingCount(dbCount - esCount); + + logger.info("Sync Status - DB: {}, ES: {}, Missing: {}", dbCount, esCount, dbCount - esCount); + return status; + + } catch (Exception e) { + logger.error("Error checking sync status: {}", e.getMessage(), e); + SyncStatus status = new SyncStatus(); + status.setError(e.getMessage()); + return status; + } + } + + /** + * Result class to track sync progress + */ + public static class SyncResult { + private int successCount = 0; + private int failureCount = 0; + private String error; + + public void addSuccess(int count) { + this.successCount += count; + } + + public void addFailure() { + this.failureCount++; + } + + public void addFailure(int count) { + this.failureCount += count; + } + + public int getSuccessCount() { + return successCount; + } + + public int getFailureCount() { + return failureCount; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + @Override + public String toString() { + return "SyncResult{successCount=" + successCount + ", failureCount=" + failureCount + + ", error='" + error + "'}"; + } + } + + public static class SyncStatus { + private long databaseCount; + private long elasticsearchCount; + private boolean synced; + private long missingCount; + private String error; + + public long getDatabaseCount() { + return databaseCount; + } + + public void setDatabaseCount(long databaseCount) { + this.databaseCount = databaseCount; + } + + public long getElasticsearchCount() { + return elasticsearchCount; + } + + public void setElasticsearchCount(long elasticsearchCount) { + this.elasticsearchCount = elasticsearchCount; + } + + public boolean isSynced() { + return synced; + } + + public void setSynced(boolean synced) { + this.synced = synced; + } + + public long getMissingCount() { + return missingCount; + } + + public void setMissingCount(long missingCount) { + this.missingCount = missingCount; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + @Override + public String toString() { + return "SyncStatus{databaseCount=" + databaseCount + ", elasticsearchCount=" + elasticsearchCount + + ", synced=" + synced + ", missingCount=" + missingCount + ", error='" + error + "'}"; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/service/elasticsearch/SyncJobService.java b/src/main/java/com/iemr/common/identity/service/elasticsearch/SyncJobService.java new file mode 100644 index 00000000..345b35cd --- /dev/null +++ b/src/main/java/com/iemr/common/identity/service/elasticsearch/SyncJobService.java @@ -0,0 +1,167 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +package com.iemr.common.identity.service.elasticsearch; + +import java.sql.Timestamp; +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.iemr.common.identity.data.elasticsearch.ElasticsearchSyncJob; +import com.iemr.common.identity.repo.elasticsearch.SyncJobRepo; + +/** + * Service to manage Elasticsearch sync jobs + */ +@Service +public class SyncJobService { + + private static final Logger logger = LoggerFactory.getLogger(SyncJobService.class); + + @Autowired + private SyncJobRepo syncJobRepository; + + @Autowired + private BeneficiaryElasticsearchIndexService syncService; + + /** + * Start a new full sync job + * Returns immediately with job ID + */ + public ElasticsearchSyncJob startFullSyncJob(String triggeredBy) { + // Check if there's already an active full sync job + if (syncJobRepository.hasActiveFullSyncJob()) { + throw new RuntimeException("A full sync job is already running. Please wait for it to complete."); + } + + // Create new job + ElasticsearchSyncJob job = new ElasticsearchSyncJob(); + job.setJobType("FULL_SYNC"); + job.setStatus("PENDING"); + job.setTriggeredBy(triggeredBy); + job.setProcessedRecords(0L); + job.setSuccessCount(0L); + job.setFailureCount(0L); + job.setCurrentOffset(0); + + // Save job to database + job = syncJobRepository.save(job); + + logger.info("Created new full sync job"); + + // Start async processing + syncService.syncAllBeneficiariesAsync(job.getJobId(), triggeredBy); + + return job; + } + + /** + * Resume a failed job from where it left off + */ + public ElasticsearchSyncJob resumeJob(Long jobId, String triggeredBy) { + ElasticsearchSyncJob job = syncJobRepository.findByJobId(jobId) + .orElseThrow(() -> new RuntimeException("Job not found: " + jobId)); + + if (!"FAILED".equals(job.getStatus())) { + throw new RuntimeException("Can only resume FAILED jobs. Current status: " + job.getStatus()); + } + + logger.info("Resuming job: jobId={}, from offset={}", jobId, job.getCurrentOffset()); + + job.setStatus("PENDING"); + job.setTriggeredBy(triggeredBy); + job = syncJobRepository.save(job); + + // Restart async processing from last offset + syncService.syncAllBeneficiariesAsync(job.getJobId(), triggeredBy); + + return job; + } + + /** + * Cancel a running job + */ + public boolean cancelJob(Long jobId) { + Optional jobOpt = syncJobRepository.findByJobId(jobId); + + if (jobOpt.isEmpty()) { + return false; + } + + ElasticsearchSyncJob job = jobOpt.get(); + + if (!job.isActive()) { + logger.warn("Cannot cancel job that is not active: jobId={}, status={}", jobId, job.getStatus()); + return false; + } + + // Mark as cancelled (the async thread will check this periodically) + job.setStatus("CANCELLED"); + job.setCompletedAt(new Timestamp(System.currentTimeMillis())); + syncJobRepository.save(job); + + logger.info("Job cancelled: jobId={}", jobId); + return true; + } + + /** + * Get job status by ID + */ + public ElasticsearchSyncJob getJobStatus(Long jobId) { + return syncJobRepository.findByJobId(jobId) + .orElseThrow(() -> new RuntimeException("Job not found: " + jobId)); + } + + /** + * Get all active jobs + */ + public List getActiveJobs() { + return syncJobRepository.findActiveJobs(); + } + + /** + * Get recent jobs (last 10) + */ + public List getRecentJobs() { + return syncJobRepository.findRecentJobs(); + } + + /** + * Check if any full sync is currently running + */ + public boolean isFullSyncRunning() { + return syncJobRepository.hasActiveFullSyncJob(); + } + + /** + * Get latest job of specific type + */ + public ElasticsearchSyncJob getLatestJobByType(String jobType) { + List jobs = syncJobRepository.findLatestJobsByType(jobType); + return jobs.isEmpty() ? null : jobs.get(0); + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/service/health/HealthService.java b/src/main/java/com/iemr/common/identity/service/health/HealthService.java new file mode 100644 index 00000000..f233d729 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/service/health/HealthService.java @@ -0,0 +1,855 @@ +/* + * AMRIT – Accessible Medical Records via Integrated Technology + * Integrated EHR (Electronic Health Records) Solution + * + * Copyright (C) "Piramal Swasthya Management and Research Institute" + * + * This file is part of AMRIT. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +package com.iemr.common.identity.service.health; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.time.Instant; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; +import javax.sql.DataSource; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.HikariPoolMXBean; +import java.lang.management.ManagementFactory; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import jakarta.annotation.PostConstruct; +import org.apache.http.HttpHost; +import org.apache.http.client.config.RequestConfig; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Service +public class HealthService { + + private static final Logger logger = LoggerFactory.getLogger(HealthService.class); + + // Status values + private static final String STATUS_KEY = "status"; + private static final String STATUS_UP = "UP"; + private static final String STATUS_DOWN = "DOWN"; + private static final String STATUS_DEGRADED = "DEGRADED"; + + // Severity values + private static final String SEVERITY_KEY = "severity"; + private static final String SEVERITY_OK = "OK"; + private static final String SEVERITY_WARNING = "WARNING"; + private static final String SEVERITY_CRITICAL = "CRITICAL"; + + // Response field keys + private static final String ERROR_KEY = "error"; + private static final String MESSAGE_KEY = "message"; + private static final String RESPONSE_TIME_KEY = "responseTimeMs"; + + // Component names + private static final String MYSQL_COMPONENT = "MySQL"; + private static final String REDIS_COMPONENT = "Redis"; + private static final String ELASTICSEARCH_TYPE = "Elasticsearch"; + + // Thresholds + private static final long RESPONSE_TIME_THRESHOLD_MS = 2_000L; + private static final long ADVANCED_CHECKS_THROTTLE_SECONDS = 30L; + private static final long ADVANCED_CHECKS_TIMEOUT_MS = 500L; + + // Diagnostic event codes + private static final String DIAGNOSTIC_LOCK_WAIT = "MYSQL_LOCK_WAIT"; + private static final String DIAGNOSTIC_SLOW_QUERIES = "MYSQL_SLOW_QUERIES"; + private static final String DIAGNOSTIC_POOL_EXHAUSTED = "MYSQL_POOL_EXHAUSTED"; + private static final String DIAGNOSTIC_LOG_TEMPLATE = "Diagnostic: {}"; + + // Elasticsearch constants + private static final long ELASTICSEARCH_FUNCTIONAL_CHECKS_THROTTLE_MS = 60_000L; + private static final int ELASTICSEARCH_CONNECT_TIMEOUT_MS = 2_000; + private static final int ELASTICSEARCH_SOCKET_TIMEOUT_MS = 2_000; + private static final int ELASTICSEARCH_CANARY_TIMEOUT_MS = 500; + private static final String ES_CLUSTER_STATUS_YELLOW = "yellow"; + private static final String ES_CLUSTER_STATUS_RED = "red"; + + private static final boolean ADVANCED_HEALTH_CHECKS_ENABLED = true; + + private final DataSource dataSource; + private final ExecutorService advancedCheckExecutor; + private final RedisTemplate redisTemplate; + private final String elasticsearchHost; + private final int elasticsearchPort; + private final boolean elasticsearchEnabled; + private final boolean elasticsearchIndexingRequired; + private final String elasticsearchTargetIndex; + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private RestClient elasticsearchRestClient; + private boolean elasticsearchClientReady = false; + + private volatile long lastAdvancedCheckTime = 0L; + private volatile AdvancedCheckResult cachedAdvancedCheckResult = null; + private final ReentrantReadWriteLock advancedCheckLock = new ReentrantReadWriteLock(); + private final AtomicBoolean advancedCheckInProgress = new AtomicBoolean(false); + + private final AtomicReference elasticsearchCache = new AtomicReference<>(null); + private volatile long lastElasticsearchFunctionalCheckTime = 0L; + private final AtomicBoolean elasticsearchCheckInProgress = new AtomicBoolean(false); + private final AtomicBoolean elasticsearchFunctionalCheckInProgress = new AtomicBoolean(false); + + + public HealthService( + DataSource dataSource, + @Autowired(required = false) RedisTemplate redisTemplate, + @Value("${elasticsearch.host:localhost}") String elasticsearchHost, + @Value("${elasticsearch.port:9200}") int elasticsearchPort, + @Value("${elasticsearch.enabled:false}") boolean elasticsearchEnabled, + @Value("${elasticsearch.target-index:amrit_data}") String elasticsearchTargetIndex, + @Value("${elasticsearch.indexing-required:false}") boolean elasticsearchIndexingRequired) { + + this.dataSource = dataSource; + this.advancedCheckExecutor = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, "health-advanced-check"); + t.setDaemon(true); + return t; + }); + this.redisTemplate = redisTemplate; + this.elasticsearchHost = elasticsearchHost; + this.elasticsearchPort = elasticsearchPort; + this.elasticsearchEnabled = elasticsearchEnabled; + this.elasticsearchIndexingRequired = elasticsearchIndexingRequired; + this.elasticsearchTargetIndex = (elasticsearchTargetIndex != null) ? elasticsearchTargetIndex : "amrit_data"; + } + + @PostConstruct + public void init() { + if (elasticsearchEnabled) { + initializeElasticsearchClient(); + } + } + + @jakarta.annotation.PreDestroy + public void cleanup() { + advancedCheckExecutor.shutdownNow(); + if (elasticsearchRestClient != null) { + try { + elasticsearchRestClient.close(); + } catch (IOException e) { + logger.warn("Error closing Elasticsearch client", e); + } + } + } + + private void initializeElasticsearchClient() { + try { + this.elasticsearchRestClient = RestClient.builder( + new HttpHost(elasticsearchHost, elasticsearchPort, "http")) + .setRequestConfigCallback(cb -> cb + .setConnectTimeout(ELASTICSEARCH_CONNECT_TIMEOUT_MS) + .setSocketTimeout(ELASTICSEARCH_SOCKET_TIMEOUT_MS)) + .build(); + this.elasticsearchClientReady = true; + logger.info("Elasticsearch client initialized (connect/socket timeout: {}ms)", + ELASTICSEARCH_CONNECT_TIMEOUT_MS); + } catch (Exception e) { + logger.warn("Failed to initialize Elasticsearch client: {}", e.getMessage()); + this.elasticsearchClientReady = false; + } + } + + + public Map checkHealth() { + Map healthStatus = new LinkedHashMap<>(); + Map components = new LinkedHashMap<>(); + boolean overallHealth = true; + + + Map mysqlStatus = checkMySQLHealth(); + components.put("mysql", mysqlStatus); + if (!isHealthy(mysqlStatus)) { + overallHealth = false; + } + + if (redisTemplate != null) { + Map redisStatus = checkRedisHealth(); + components.put("redis", redisStatus); + if (!isHealthy(redisStatus)) { + overallHealth = false; + } + } + if (elasticsearchEnabled && elasticsearchClientReady) { + Map esStatus = checkElasticsearchHealth(); + components.put("elasticsearch", esStatus); + if (!isHealthy(esStatus)) { + overallHealth = false; + } + } + + healthStatus.put(STATUS_KEY, overallHealth ? STATUS_UP : STATUS_DOWN); + healthStatus.put("timestamp", Instant.now().toString()); + healthStatus.put("components", components); + logger.info("Health check completed – overall: {}", overallHealth ? STATUS_UP : STATUS_DOWN); + return healthStatus; + } + + private Map checkMySQLHealth() { + Map details = new LinkedHashMap<>(); + details.put("type", MYSQL_COMPONENT); + + return performHealthCheck(MYSQL_COMPONENT, details, () -> { + try (Connection connection = dataSource.getConnection()) { + if (!connection.isValid(2)) { + return new HealthCheckResult(false, "Connection validation failed", false); + } + try (PreparedStatement stmt = connection.prepareStatement("SELECT 1 as health_check")) { + stmt.setQueryTimeout(3); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next() && rs.getInt(1) == 1) { + boolean isDegraded = performAdvancedMySQLChecksWithThrottle(); + return new HealthCheckResult(true, null, isDegraded); + } + } + } + return new HealthCheckResult(false, "Unexpected query result", false); + } catch (Exception e) { + throw new IllegalStateException(MYSQL_COMPONENT + " connection failed: " + e.getMessage(), e); + } + }); + } + + private Map checkRedisHealth() { + Map details = new LinkedHashMap<>(); + details.put("type", REDIS_COMPONENT); + + return performHealthCheck(REDIS_COMPONENT, details, () -> { + try { + String pong = redisTemplate.execute((RedisCallback) conn -> conn.ping()); + if ("PONG".equals(pong)) { + return new HealthCheckResult(true, null, false); + } + return new HealthCheckResult(false, "Unexpected ping response", false); + } catch (Exception e) { + throw new IllegalStateException("Redis health check failed", e); + } + }); + } + + private Map checkElasticsearchHealth() { + Map details = new LinkedHashMap<>(); + details.put("type", ELASTICSEARCH_TYPE); + return performHealthCheck(ELASTICSEARCH_TYPE, details, this::getElasticsearchHealthResult); + } + + private HealthCheckResult getElasticsearchHealthResult() { + if (!elasticsearchClientReady || elasticsearchRestClient == null) { + return new HealthCheckResult(false, "Service unavailable", false); + } + + long now = System.currentTimeMillis(); + HealthCheckResult cached = getCachedElasticsearchHealth(now); + if (cached != null) { + return cached; + } + + return performElasticsearchHealthCheckWithCache(now); + } + + private HealthCheckResult getCachedElasticsearchHealth(long now) { + ElasticsearchCacheEntry cached = elasticsearchCache.get(); + if (cached != null && !cached.isExpired(now)) { + logger.debug("Returning cached ES health (age: {}ms, status: {})", + now - cached.timestamp, cached.result.isHealthy ? STATUS_UP : STATUS_DOWN); + return cached.result; + } + return null; + } + + private HealthCheckResult performElasticsearchHealthCheckWithCache(long now) { + // Single-flight: only one thread probes ES; others use stale cache + if (!elasticsearchCheckInProgress.compareAndSet(false, true)) { + ElasticsearchCacheEntry fallback = elasticsearchCache.get(); + if (fallback != null) { + logger.debug("ES check already in progress – using stale cache"); + return fallback.result; + } + // On cold start with concurrent requests, return DEGRADED (not DOWN) until first result + logger.debug("ES check already in progress with no cache – returning DEGRADED"); + return new HealthCheckResult(true, null, true); + } + + try { + HealthCheckResult result = performElasticsearchHealthCheck(); + elasticsearchCache.set(new ElasticsearchCacheEntry(result, now)); + return result; + } catch (Exception e) { + logger.debug("Elasticsearch health check exception: {}", e.getClass().getSimpleName()); + HealthCheckResult errorResult = new HealthCheckResult(false, "Service unavailable", false); + elasticsearchCache.set(new ElasticsearchCacheEntry(errorResult, now)); + return errorResult; + } finally { + elasticsearchCheckInProgress.set(false); + } + } + + private HealthCheckResult performElasticsearchHealthCheck() { + ClusterHealthStatus healthStatus = getClusterHealthStatus(); + if (healthStatus == null) { + // Cluster health unavailable; check if index is reachable to determine degradation vs DOWN + if (indexExists()) { + logger.debug("Cluster health unavailable but index is reachable – returning DEGRADED"); + return new HealthCheckResult(true, null, true); // DEGRADED: index reachable but cluster health offline + } + logger.warn("Cluster health unavailable and index unreachable"); + return new HealthCheckResult(false, "Cluster health unavailable", false); + } + if (ES_CLUSTER_STATUS_RED.equals(healthStatus.status)) { + return new HealthCheckResult(false, "Cluster red", false); + } + + boolean isDegraded = ES_CLUSTER_STATUS_YELLOW.equals(healthStatus.status); + + String functionalCheckError = shouldRunFunctionalChecks() + ? performThrottledFunctionalChecksWithError() + : null; + + if (functionalCheckError != null) { + return new HealthCheckResult(false, functionalCheckError, false); + } + return new HealthCheckResult(true, null, isDegraded); + } + + private ClusterHealthStatus getClusterHealthStatus() { + try { + Request request = new Request("GET", "/_cluster/health"); + applyTimeouts(request, ELASTICSEARCH_CONNECT_TIMEOUT_MS); + var response = elasticsearchRestClient.performRequest(request); + if (response.getStatusLine().getStatusCode() != 200) { + logger.debug("Cluster health returned HTTP {}", response.getStatusLine().getStatusCode()); + return null; + } + String body = new String(response.getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8); + JsonNode root = objectMapper.readTree(body); + String status = root.path(STATUS_KEY).asText(); + if (status == null || status.isEmpty()) { + logger.debug("Could not parse cluster status"); + return null; + } + return new ClusterHealthStatus(status); + } catch (java.net.ConnectException | java.net.SocketTimeoutException e) { + logger.debug("Elasticsearch network error: {}", e.getClass().getSimpleName()); + } catch (IOException e) { + logger.debug("Elasticsearch IO error: {}", e.getClass().getSimpleName()); + } catch (Exception e) { + logger.debug("Elasticsearch health check error: {}", e.getClass().getSimpleName()); + } + return null; + } + + private boolean shouldRunFunctionalChecks() { + return (System.currentTimeMillis() - lastElasticsearchFunctionalCheckTime) + >= ELASTICSEARCH_FUNCTIONAL_CHECKS_THROTTLE_MS; + } + + private String performThrottledFunctionalChecksWithError() { + if (!elasticsearchFunctionalCheckInProgress.compareAndSet(false, true)) { + logger.debug("Functional checks already in progress – skipping"); + return null; + } + try { + long now = System.currentTimeMillis(); + + if (!indexExists()) { + logger.warn("Functional check failed: index missing"); + lastElasticsearchFunctionalCheckTime = now; + return "Index missing"; + } + + ReadOnlyCheckResult readOnlyResult = isClusterReadOnly(); + if (readOnlyResult.isReadOnly) { + logger.warn("Functional check failed: cluster is read-only"); + lastElasticsearchFunctionalCheckTime = now; + return "Read-only block"; + } + if (readOnlyResult.isUnableToDetermine) { + logger.warn("Functional check degraded: unable to determine read-only state"); + } + + CanaryWriteResult canaryResult = performCanaryWriteProbe(); + if (!canaryResult.success) { + if (elasticsearchIndexingRequired) { + logger.warn("Functional check failed: canary write unsuccessful – {}", canaryResult.errorCategory); + lastElasticsearchFunctionalCheckTime = now; + return "Canary write failed: " + canaryResult.errorCategory; + } else { + logger.debug("Canary write unsuccessful but indexing not required: {}", canaryResult.errorCategory); + } + } + + lastElasticsearchFunctionalCheckTime = now; + return null; + } finally { + elasticsearchFunctionalCheckInProgress.set(false); + } + } + + private boolean indexExists() { + try { + Request request = new Request("HEAD", "/" + elasticsearchTargetIndex); + applyTimeouts(request, ELASTICSEARCH_CANARY_TIMEOUT_MS); + var response = elasticsearchRestClient.performRequest(request); + return response.getStatusLine().getStatusCode() == 200; + } catch (Exception e) { + logger.debug("Index existence check failed: {}", e.getClass().getSimpleName()); + return false; + } + } + + private ReadOnlyCheckResult isClusterReadOnly() { + try { + Request request = new Request("GET", "/_cluster/settings?include_defaults=true"); + applyTimeouts(request, ELASTICSEARCH_CONNECT_TIMEOUT_MS); + var response = elasticsearchRestClient.performRequest(request); + if (response.getStatusLine().getStatusCode() != 200) { + logger.debug("Cluster settings returned HTTP {}", response.getStatusLine().getStatusCode()); + return new ReadOnlyCheckResult(false, true); + } + String body = new String(response.getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8); + JsonNode root = objectMapper.readTree(body); + boolean hasReadOnlyBlock = hasReadOnlyFlag(root, "read_only"); + boolean hasReadOnlyDeleteBlock = hasReadOnlyFlag(root, "read_only_allow_delete"); + return new ReadOnlyCheckResult(hasReadOnlyBlock || hasReadOnlyDeleteBlock, false); + } catch (java.net.SocketTimeoutException e) { + logger.debug("Read-only check timeout"); + } catch (IOException e) { + logger.debug("Read-only check IO error: {}", e.getClass().getSimpleName()); + } catch (Exception e) { + logger.debug("Read-only check failed: {}", e.getClass().getSimpleName()); + } + return new ReadOnlyCheckResult(false, true); + } + + private boolean hasReadOnlyFlag(JsonNode root, String flag) { + String[] paths = { + "/persistent/cluster/blocks/" + flag, + "/transient/cluster/blocks/" + flag, + "/defaults/cluster/blocks/" + flag + }; + for (String path : paths) { + try { + JsonNode node = root.at(path); + if (node != null && !node.isMissingNode() && + ((node.isBoolean() && node.asBoolean()) || + (node.isTextual() && "true".equalsIgnoreCase(node.asText())))) { + logger.debug("Found read-only flag at {}", path); + return true; + } + } catch (Exception e) { + logger.debug("Error checking JSON pointer {}: {}", path, e.getClass().getSimpleName()); + } + } + return false; + } + + private void applyTimeouts(Request request, int timeoutMs) { + RequestOptions options = RequestOptions.DEFAULT.toBuilder() + .setRequestConfig(RequestConfig.custom() + .setConnectTimeout(timeoutMs) + .setSocketTimeout(timeoutMs) + .build()) + .build(); + request.setOptions(options); + } + + private void performCanaryDelete(String canaryDocId) { + try { + Request deleteRequest = new Request("DELETE", "/" + elasticsearchTargetIndex + "/_doc/" + canaryDocId); + applyTimeouts(deleteRequest, ELASTICSEARCH_CANARY_TIMEOUT_MS); + elasticsearchRestClient.performRequest(deleteRequest); + } catch (Exception e) { + logger.debug("Canary delete warning: {}", e.getClass().getSimpleName()); + } + } + + private CanaryWriteResult performCanaryWriteProbe() { + String canaryDocId = "health-check-canary"; + try { + String canaryBody = "{\"probe\":true,\"timestamp\":\"" + Instant.now() + "\"}"; + + // FIX: Use PUT (not POST) for a document with a specific ID + Request writeRequest = new Request("PUT", "/" + elasticsearchTargetIndex + "/_doc/" + canaryDocId); + applyTimeouts(writeRequest, ELASTICSEARCH_CANARY_TIMEOUT_MS); + writeRequest.setEntity(new org.apache.http.entity.StringEntity(canaryBody, StandardCharsets.UTF_8)); + writeRequest.addParameter("refresh", "true"); + + var writeResponse = elasticsearchRestClient.performRequest(writeRequest); + if (writeResponse.getStatusLine().getStatusCode() > 299) { + logger.debug("Canary write failed with HTTP {}", writeResponse.getStatusLine().getStatusCode()); + return new CanaryWriteResult(false, "Write rejected"); + } + + performCanaryDelete(canaryDocId); + return new CanaryWriteResult(true, null); + } catch (java.net.SocketTimeoutException e) { + logger.debug("Canary probe timeout"); + return new CanaryWriteResult(false, "Timeout"); + } catch (java.net.ConnectException e) { + logger.debug("Canary probe connection refused"); + return new CanaryWriteResult(false, "Connection refused"); + } catch (Exception e) { + logger.debug("Canary probe failed: {}", e.getClass().getSimpleName()); + return new CanaryWriteResult(false, "Write failed"); + } + } + + + private Map performHealthCheck(String componentName, + Map details, + Supplier checker) { + Map status = new LinkedHashMap<>(); + long startTime = System.currentTimeMillis(); + try { + HealthCheckResult result = checker.get(); + long responseTime = System.currentTimeMillis() - startTime; + details.put(RESPONSE_TIME_KEY, responseTime); + + if (result.isHealthy) { + buildHealthyStatus(status, componentName, responseTime, result); + } else { + buildUnhealthyStatus(status, details, componentName, result); + } + status.put("details", details); + return status; + } catch (Exception e) { + long responseTime = System.currentTimeMillis() - startTime; + return buildExceptionStatus(status, details, componentName, e, responseTime); + } + } + + private void buildHealthyStatus(Map status, + String componentName, long responseTime, HealthCheckResult result) { + logger.debug("{} health check: {} ({}ms)", + componentName, result.isDegraded ? STATUS_DEGRADED : STATUS_UP, responseTime); + status.put(STATUS_KEY, result.isDegraded ? STATUS_DEGRADED : STATUS_UP); + status.put(SEVERITY_KEY, determineSeverity(true, responseTime, result.isDegraded)); + if (result.error != null) { + status.put(MESSAGE_KEY, result.error); + } + } + + private void buildUnhealthyStatus(Map status, Map details, + String componentName, HealthCheckResult result) { + String internalError = (result.error != null) ? result.error : "Health check failed"; + logger.warn("{} health check failed: {}", componentName, internalError); + status.put(STATUS_KEY, STATUS_DOWN); + status.put(SEVERITY_KEY, SEVERITY_CRITICAL); + // Sanitized outward message – no topology leakage + details.put(ERROR_KEY, "Dependency unavailable"); + // For Elasticsearch, sanitize detailed failure reasons; keep real reason in logs only + String exposedCategory = ELASTICSEARCH_TYPE.equals(componentName) + ? "DEPENDENCY_FAILURE" + : internalError; + details.put("errorCategory", exposedCategory); + details.put("errorType", "CheckFailed"); + } + + private Map buildExceptionStatus(Map status, Map details, + String componentName, Exception e, long responseTime) { + logger.error("{} health check threw exception: {}", componentName, e.getMessage(), e); + status.put(STATUS_KEY, STATUS_DOWN); + status.put(SEVERITY_KEY, SEVERITY_CRITICAL); + details.put(RESPONSE_TIME_KEY, responseTime); + // FIX: Sanitize error message – do not expose raw exception detail to consumers + details.put(ERROR_KEY, "Dependency unavailable"); + details.put("errorCategory", "CheckException"); + details.put("errorType", "Exception"); + status.put("details", details); + return status; + } + + private String determineSeverity(boolean isHealthy, long responseTimeMs, boolean isDegraded) { + if (!isHealthy) return SEVERITY_CRITICAL; + if (isDegraded) return SEVERITY_WARNING; + if (responseTimeMs > RESPONSE_TIME_THRESHOLD_MS) return SEVERITY_WARNING; + return SEVERITY_OK; + } + + private boolean isHealthy(Map componentStatus) { + Object s = componentStatus.get(STATUS_KEY); + return STATUS_UP.equals(s) || STATUS_DEGRADED.equals(s); + } + + private boolean performAdvancedMySQLChecksWithThrottle() { + if (!ADVANCED_HEALTH_CHECKS_ENABLED) { + return false; + } + + long currentTime = System.currentTimeMillis(); + + // --- Phase 1: try to serve from cache (read lock) --- + advancedCheckLock.readLock().lock(); + try { + if (cachedAdvancedCheckResult != null && + (currentTime - lastAdvancedCheckTime) < ADVANCED_CHECKS_THROTTLE_SECONDS * 1_000L) { + return cachedAdvancedCheckResult.isDegraded; + } + } finally { + advancedCheckLock.readLock().unlock(); + } + + // --- Phase 2: single-flight guard --- + if (!advancedCheckInProgress.compareAndSet(false, true)) { + // Another thread is refreshing – return stale cache (safe fallback) + advancedCheckLock.readLock().lock(); + try { + return cachedAdvancedCheckResult != null && cachedAdvancedCheckResult.isDegraded; + } finally { + advancedCheckLock.readLock().unlock(); + } + } + + // --- Phase 3: run DB checks outside any lock --- + try { + AdvancedCheckResult result = performAdvancedMySQLChecks(); + + // --- Phase 4: write-lock for atomic cache update --- + advancedCheckLock.writeLock().lock(); + try { + lastAdvancedCheckTime = System.currentTimeMillis(); + cachedAdvancedCheckResult = result; + return result.isDegraded; + } finally { + advancedCheckLock.writeLock().unlock(); + } + } finally { + advancedCheckInProgress.set(false); + } + } + + private AdvancedCheckResult performAdvancedMySQLChecks() { + try (Connection connection = dataSource.getConnection()) { + return executeAdvancedCheckAsync(connection); + } catch (Exception e) { + logger.debug("Failed to get connection for advanced checks: {}", e.getMessage()); + return new AdvancedCheckResult(true); + } + } + + private AdvancedCheckResult executeAdvancedCheckAsync(Connection connection) { + Future future = advancedCheckExecutor.submit( + () -> performAdvancedCheckLogic(connection)); + try { + return future.get(ADVANCED_CHECKS_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS); + } catch (java.util.concurrent.TimeoutException e) { + logger.debug("Advanced checks timed out – marking degraded"); + future.cancel(true); + } catch (ExecutionException e) { + if (e.getCause() instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + logger.debug("Advanced checks execution failed – marking degraded"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.debug("Advanced checks interrupted – marking degraded"); + } catch (Exception e) { + logger.debug("Advanced checks encountered exception – marking degraded"); + } + return new AdvancedCheckResult(true); + } + + private AdvancedCheckResult performAdvancedCheckLogic(Connection connection) { + try { + boolean hasIssues = false; + + if (hasLockWaits(connection)) { + logger.warn(DIAGNOSTIC_LOG_TEMPLATE, DIAGNOSTIC_LOCK_WAIT); + hasIssues = true; + } + if (hasSlowQueries(connection)) { + logger.warn(DIAGNOSTIC_LOG_TEMPLATE, DIAGNOSTIC_SLOW_QUERIES); + hasIssues = true; + } + if (hasConnectionPoolExhaustion()) { + logger.warn(DIAGNOSTIC_LOG_TEMPLATE, DIAGNOSTIC_POOL_EXHAUSTED); + hasIssues = true; + } + return new AdvancedCheckResult(hasIssues); + } catch (Exception e) { + logger.debug("Advanced check logic encountered exception"); + return new AdvancedCheckResult(true); + } + } + + private boolean hasLockWaits(Connection connection) { + String sql = + "SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST " + + "WHERE (state = 'Waiting for table metadata lock' " + + " OR state = 'Waiting for row lock' " + + " OR state = 'Waiting for lock') " + + "AND user = USER()"; + try (PreparedStatement stmt = connection.prepareStatement(sql)) { + stmt.setQueryTimeout(2); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + return rs.getInt(1) > 0; + } + } + } catch (Exception e) { + logger.debug("Could not check for lock waits"); + } + return false; + } + + private boolean hasSlowQueries(Connection connection) { + String sql = + "SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST " + + "WHERE command != 'Sleep' AND time > ? AND user = SUBSTRING_INDEX(USER(), '@', 1)"; + try (PreparedStatement stmt = connection.prepareStatement(sql)) { + stmt.setQueryTimeout(2); + stmt.setInt(1, 10); // queries running > 10 seconds + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + return rs.getInt(1) > 3; // more than 3 slow queries + } + } + } catch (Exception e) { + logger.debug("Could not check for slow queries"); + } + return false; + } + + private boolean hasConnectionPoolExhaustion() { + if (dataSource instanceof HikariDataSource hikariDataSource) { + try { + HikariPoolMXBean poolMXBean = hikariDataSource.getHikariPoolMXBean(); + if (poolMXBean != null) { + int activeConnections = poolMXBean.getActiveConnections(); + int maxPoolSize = hikariDataSource.getMaximumPoolSize(); + int threshold = (int) (maxPoolSize * 0.8); + return activeConnections > threshold; + } + } catch (Exception e) { + logger.debug("Could not retrieve HikariCP pool metrics directly"); + } + } + return checkPoolMetricsViaJMX(); + } + + private boolean checkPoolMetricsViaJMX() { + try { + MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); + ObjectName objectName = new ObjectName("com.zaxxer.hikari:type=Pool (*)"); + var mBeans = mBeanServer.queryMBeans(objectName, null); + for (var mBean : mBeans) { + if (evaluatePoolMetrics(mBeanServer, mBean.getObjectName())) { + return true; + } + } + } catch (Exception e) { + logger.debug("Could not access HikariCP pool metrics via JMX"); + } + logger.debug("Pool exhaustion check disabled: HikariCP metrics unavailable"); + return false; + } + + private boolean evaluatePoolMetrics(MBeanServer mBeanServer, ObjectName objectName) { + try { + Integer activeConnections = (Integer) mBeanServer.getAttribute(objectName, "ActiveConnections"); + Integer maximumPoolSize = (Integer) mBeanServer.getAttribute(objectName, "MaximumPoolSize"); + if (activeConnections != null && maximumPoolSize != null) { + int threshold = (int) (maximumPoolSize * 0.8); + return activeConnections > threshold; + } + } catch (Exception e) { + // Continue to next MBean + } + return false; + } + + + private static class AdvancedCheckResult { + final boolean isDegraded; + AdvancedCheckResult(boolean isDegraded) { this.isDegraded = isDegraded; } + } + + private static class HealthCheckResult { + final boolean isHealthy; + final String error; + final boolean isDegraded; + HealthCheckResult(boolean isHealthy, String error, boolean isDegraded) { + this.isHealthy = isHealthy; + this.error = error; + this.isDegraded = isDegraded; + } + } + + private static class ElasticsearchCacheEntry { + final HealthCheckResult result; + final long timestamp; + ElasticsearchCacheEntry(HealthCheckResult result, long timestamp) { + this.result = result; + this.timestamp = timestamp; + } + /** UP results cache for 30 s; DOWN results for 5 s for faster recovery. */ + boolean isExpired(long now) { + long ttlMs = result.isHealthy ? 30_000L : 5_000L; + return (now - timestamp) >= ttlMs; + } + } + + private static class ClusterHealthStatus { + final String status; + ClusterHealthStatus(String status) { this.status = status; } + } + + private static class ReadOnlyCheckResult { + final boolean isReadOnly; + final boolean isUnableToDetermine; + ReadOnlyCheckResult(boolean isReadOnly, boolean isUnableToDetermine) { + this.isReadOnly = isReadOnly; + this.isUnableToDetermine = isUnableToDetermine; + } + } + + private static class CanaryWriteResult { + final boolean success; + final String errorCategory; + CanaryWriteResult(boolean success, String errorCategory) { + this.success = success; + this.errorCategory = errorCategory; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/iemr/common/identity/utils/CookieUtil.java b/src/main/java/com/iemr/common/identity/utils/CookieUtil.java index 84f54526..88be3f09 100644 --- a/src/main/java/com/iemr/common/identity/utils/CookieUtil.java +++ b/src/main/java/com/iemr/common/identity/utils/CookieUtil.java @@ -1,5 +1,6 @@ package com.iemr.common.identity.utils; +import java.util.Arrays; import java.util.Optional; import org.springframework.stereotype.Service; @@ -9,20 +10,26 @@ @Service public class CookieUtil { - - public Optional getCookieValue(HttpServletRequest request, String cookieName) { - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookieName.equals(cookie.getName())) { - return Optional.of(cookie.getValue()); - } - } - } - return Optional.empty(); - } - public String getJwtTokenFromCookie(HttpServletRequest request) { - return getCookieValue(request, "Jwttoken").orElse(null); - } + public Optional getCookieValue(HttpServletRequest request, String cookieName) { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookieName.equals(cookie.getName())) { + return Optional.of(cookie.getValue()); + } + } + } + return Optional.empty(); + } + + public static String getJwtTokenFromCookie(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + if (cookies == null) { + return null; // No cookies present, return null safely + } + return Arrays.stream(cookies).filter(cookie -> "Jwttoken".equals(cookie.getName())).map(Cookie::getValue) + .findFirst().orElse(null); + } + } diff --git a/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java b/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java index 49e48265..af2539ff 100644 --- a/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java +++ b/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java @@ -109,7 +109,7 @@ private User getUserFromCache(String userId) { private User fetchUserFromDB(String userId) { String redisKey = "user_" + userId; // Redis key format List users = jdbcTemplate.query( - "SELECT * FROM m_user WHERE UserID = ? AND Deleted = false", + "SELECT * FROM db_iemr.m_user WHERE UserID = ? AND Deleted = false", new BeanPropertyRowMapper<>(User.class), userId); if (users.isEmpty()) { diff --git a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java index 6d5c55f9..af81ea34 100644 --- a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java @@ -43,7 +43,14 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo return; } String path = request.getRequestURI(); - logger.info("JwtUserIdValidationFilter invoked for path: " + path); + logger.info("JwtUserIdValidationFilter invoked for path: {}", path); + + // Skip JWT validation for public endpoints + if (path.equals("/health") || path.equals("/version")) { + logger.info("Public endpoint accessed: {} - skipping JWT validation", path); + filterChain.doFilter(servletRequest, servletResponse); + return; + } // Log cookies for debugging Cookie[] cookies = request.getCookies(); @@ -76,10 +83,10 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo } } else { String userAgent = request.getHeader("User-Agent"); - logger.info("User-Agent: " + userAgent); + logger.info("User-Agent: {}", userAgent); if (userAgent != null && isMobileClient(userAgent) && authHeader != null) { try { - logger.info("Common-API incoming userAget : " + userAgent); + logger.info("Common-API incoming userAgent: {}", userAgent); UserAgentContext.setUserAgent(userAgent); filterChain.doFilter(servletRequest, servletResponse); } finally { @@ -162,6 +169,7 @@ private boolean isMobileClient(String userAgent) { return userAgent.contains("okhttp") || userAgent.contains("java/"); // iOS (custom clients) } + @SuppressWarnings("static-access") private String getJwtTokenFromCookies(HttpServletRequest request) { return cookieUtil.getJwtTokenFromCookie(request); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1780d8b6..12be1b44 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,49 +7,151 @@ spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.Im spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl # The SQL dialect makes Hibernate generate better SQL for the chosen database -spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect -#Below lines are added for security reasons +# ============================================================================ +# OPTIMIZED HIKARICP SETTINGS FOR HIGH CONCURRENCY +# ============================================================================ +# Connection Pool Size - CRITICAL for handling multiple concurrent requests +# Rule: (max-pool-size) = (number of CPUs * 2) + effectiveSpindles +# For 8 CPUs: 8*2 + 4 = 20-30 is optimal +spring.datasource.hikari.maximum-pool-size=40 +spring.datasource.hikari.minimum-idle=20 + +# Connection Timeouts - REDUCED for faster failure detection +spring.datasource.hikari.connection-timeout=20000 +spring.datasource.hikari.validation-timeout=3000 + +# Idle/Lifetime - Prevents stale connections +spring.datasource.hikari.idle-timeout=300000 +spring.datasource.hikari.max-lifetime=1800000 +spring.datasource.hikari.keepalive-time=60000 + +# Leak Detection - CRITICAL for debugging connection issues +spring.datasource.hikari.leak-detection-threshold=30000 + +# MySQL Connection Properties - Keeps connections alive +spring.datasource.hikari.data-source-properties.cachePrepStmts=true +spring.datasource.hikari.data-source-properties.prepStmtCacheSize=500 +spring.datasource.hikari.data-source-properties.prepStmtCacheSqlLimit=4096 +spring.datasource.hikari.data-source-properties.useServerPrepStmts=true +spring.datasource.hikari.data-source-properties.rewriteBatchedStatements=true +spring.datasource.hikari.data-source-properties.maintainTimeStats=false + +# CRITICAL: Prevents MySQL "connection lost" errors +spring.datasource.hikari.data-source-properties.autoReconnect=true +spring.datasource.hikari.data-source-properties.tcpKeepAlive=true +spring.datasource.hikari.data-source-properties.socketTimeout=60000 +spring.datasource.hikari.data-source-properties.connectTimeout=10000 + +# Health check +spring.datasource.hikari.health-check-properties.connectivityCheckTimeoutMs=3000 + +# ============================================================================ +# HIBERNATE PERFORMANCE TUNING +# ============================================================================ + +# Batch processing for bulk operations +spring.jpa.properties.hibernate.jdbc.fetch_size=100 +spring.jpa.properties.hibernate.jdbc.batch_size=50 +spring.jpa.properties.hibernate.order_inserts=true +spring.jpa.properties.hibernate.order_updates=true + +# Query timeout (60 seconds) +spring.jpa.properties.hibernate.query.timeout=60000 + +# Statement cache +spring.jpa.properties.hibernate.query.plan_cache_max_size=2048 +spring.jpa.properties.hibernate.query.plan_parameter_metadata_max_size=128 + +# ============================================================================ +# REDIS SESSION MANAGEMENT +# ============================================================================ spring.session.store-type=redis spring.redis.password= spring.redis.port=6379 -## Below values are needed for extending the expiry time and extend expiry time. +# Redis connection pool +spring.redis.lettuce.pool.max-active=20 +spring.redis.lettuce.pool.max-idle=10 +spring.redis.lettuce.pool.min-idle=5 +spring.redis.lettuce.pool.max-wait=3000ms + +# Session expiry iemr.extend.expiry.time=true iemr.session.expiry.time=7200 enableIPValidation=false -#logging.pattern.level=DEBUG +# ============================================================================ +# ELASTICSEARCH CONFIGURATION - OPTIMIZED +# ============================================================================ + +# Connection timeouts - Balanced for reliability +elasticsearch.connection.timeout=5000 +elasticsearch.socket.timeout=30000 +elasticsearch.max.retry.timeout=60000 + +# Connection pooling - INCREASED for concurrent searches +elasticsearch.max.connections=100 +elasticsearch.max.connections.per.route=50 + +# Request configuration +elasticsearch.request.timeout=10000 +elasticsearch.max.result.window=10000 + +# Bulk indexing - OPTIMIZED for background sync +elasticsearch.bulk.size=1000 +elasticsearch.bulk.concurrent.requests=6 +elasticsearch.bulk.flush.interval=10s + +# Search performance - FAST response for user queries +elasticsearch.search.default.size=100 +elasticsearch.search.max.size=500 +elasticsearch.search.timeout=5s + +# Query cache - CRITICAL for repeated searches +elasticsearch.query.cache.enabled=true +elasticsearch.query.cache.size=15% + +# Request cache - Speeds up identical queries +elasticsearch.request.cache.enabled=true + +# Circuit breaker - Prevents OOM +elasticsearch.circuit.breaker.enabled=true +elasticsearch.circuit.breaker.limit=90% + +# Async operations thread pool +elasticsearch.async.thread.pool.size=10 +elasticsearch.async.thread.pool.queue.size=2000 + +elasticsearch.bulk.refresh.interval=5 + +# ============================================================================ +# LOGGING - Balanced for debugging +# ============================================================================ logging.level.root=INFO logging.level.org.springframework.web=INFO logging.level.org.hibernate=INFO logging.level.com.iemr=DEBUG logging.level.org.springframework=INFO +logging.level.com.zaxxer.hikari=INFO +logging.level.com.zaxxer.hikari.pool.HikariPool=DEBUG -spring.datasource.tomcat.initial-size=5 -spring.datasource.tomcat.max-idle=15 -spring.datasource.tomcat.max-active=30 -spring.datasource.tomcat.min-idle=5 -spring.datasource.tomcat.min-evictable-idle-time-millis=15000 -spring.datasource.tomcat.remove-abandoned=true -spring.datasource.tomcat.logAbandoned=true -spring.datasource.continue-on-error=true -spring.datasource.tomcat.remove-abandoned-timeout=600 -door-to-door-page-size=2 +# Log slow queries (helps identify bottlenecks) +logging.level.org.hibernate.SQL=WARN +spring.jpa.properties.hibernate.show_sql=false -#Get-HRP-Status +# ============================================================================ +# APPLICATION SPECIFIC +# ============================================================================ +door-to-door-page-size=2 get-HRP-Status=ANC/getHRPStatus - -#Get Beneficiary ABHA getHealthID=healthID/getBenhealthID - -## commit miss error in ci - - spring.main.allow-bean-definition-overriding=true spring.main.allow-circular-references=true -#springfox.documentation.swagger-ui.enabled=true + jwt.access.expiration=86400000 jwt.refresh.expiration=604800000 +