diff --git a/.github/workflows/commit-lint.yml b/.github/workflows/commit-lint.yml
index d6a5d36..eed2fc9 100644
--- a/.github/workflows/commit-lint.yml
+++ b/.github/workflows/commit-lint.yml
@@ -17,6 +17,9 @@ jobs:
submodules: true
fetch-depth: 0
+ - name: Fetch base branch
+ run: git fetch origin ${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }}
+
- name: Setup Node.js
uses: actions/setup-node@v4
with:
diff --git a/pom.xml b/pom.xml
index 712140d..db644db 100644
--- a/pom.xml
+++ b/pom.xml
@@ -200,6 +200,10 @@
spring-session-data-redis
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+
org.mockito
mockito-core
diff --git a/src/main/java/com/iemr/admin/service/health/HealthService.java b/src/main/java/com/iemr/admin/service/health/HealthService.java
index ae41b8e..820cf29 100644
--- a/src/main/java/com/iemr/admin/service/health/HealthService.java
+++ b/src/main/java/com/iemr/admin/service/health/HealthService.java
@@ -24,14 +24,19 @@
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
-import java.util.HashMap;
+import java.time.Instant;
+import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Properties;
import javax.sql.DataSource;
+import org.bson.Document;
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.mongodb.core.MongoTemplate;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@@ -41,6 +46,7 @@ public class HealthService {
private static final Logger logger = LoggerFactory.getLogger(HealthService.class);
private static final String DB_HEALTH_CHECK_QUERY = "SELECT 1 as health_check";
+ private static final String DB_VERSION_QUERY = "SELECT VERSION()";
@Autowired
private DataSource dataSource;
@@ -48,72 +54,321 @@ public class HealthService {
@Autowired(required = false)
private RedisTemplate redisTemplate;
+ @Autowired(required = false)
+ private MongoTemplate mongoTemplate;
+
+ @Value("${spring.datasource.url:unknown}")
+ private String dbUrl;
+
+ @Value("${spring.redis.host:localhost}")
+ private String redisHost;
+
+ @Value("${spring.redis.port:6379}")
+ private int redisPort;
+
+ @Value("${spring.data.mongodb.host:localhost}")
+ private String mongoHost;
+
+ @Value("${spring.data.mongodb.port:27017}")
+ private int mongoPort;
+
+ @Value("${spring.data.mongodb.database:amrit}")
+ private String mongoDatabase;
+
public Map checkHealth() {
- Map healthStatus = new HashMap<>();
+ Map healthStatus = new LinkedHashMap<>();
+ Map components = new LinkedHashMap<>();
boolean overallHealth = true;
- // Check database connectivity (details logged internally, not exposed)
- boolean dbHealthy = checkDatabaseHealthInternal();
- if (!dbHealthy) {
+ // Check MySQL connectivity
+ Map mysqlStatus = checkMySQLHealth();
+ components.put("mysql", mysqlStatus);
+ if (!"UP".equals(mysqlStatus.get("status"))) {
overallHealth = false;
}
- // Check Redis connectivity if configured (details logged internally)
+ // Check Redis connectivity if configured
if (redisTemplate != null) {
- boolean redisHealthy = checkRedisHealthInternal();
- if (!redisHealthy) {
+ Map redisStatus = checkRedisHealth();
+ components.put("redis", redisStatus);
+ if (!"UP".equals(redisStatus.get("status"))) {
+ overallHealth = false;
+ }
+ }
+
+ // Check MongoDB connectivity if configured
+ if (mongoTemplate != null) {
+ Map mongoStatus = checkMongoDBHealth();
+ components.put("mongodb", mongoStatus);
+ if (!"UP".equals(mongoStatus.get("status"))) {
overallHealth = false;
}
}
healthStatus.put("status", overallHealth ? "UP" : "DOWN");
+ healthStatus.put("timestamp", Instant.now().toString());
+ healthStatus.put("components", components);
logger.info("Health check completed - Overall status: {}", overallHealth ? "UP" : "DOWN");
return healthStatus;
}
- private boolean checkDatabaseHealthInternal() {
+ private Map checkMySQLHealth() {
+ Map status = new LinkedHashMap<>();
+ Map details = new LinkedHashMap<>();
long startTime = System.currentTimeMillis();
-
+
+ // Add connection details
+ details.put("type", "MySQL");
+ details.put("host", extractHost(dbUrl));
+ details.put("port", extractPort(dbUrl));
+ details.put("database", extractDatabaseName(dbUrl));
+
try (Connection connection = dataSource.getConnection()) {
- boolean isConnectionValid = connection.isValid(2); // 2 second timeout per best practices
-
+ // 2 second timeout per best practices
+ boolean isConnectionValid = connection.isValid(2);
+
if (isConnectionValid) {
+ // 3 second query timeout
try (PreparedStatement stmt = connection.prepareStatement(DB_HEALTH_CHECK_QUERY)) {
- stmt.setQueryTimeout(3); // 3 second query timeout
+ stmt.setQueryTimeout(3);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next() && rs.getInt(1) == 1) {
long responseTime = System.currentTimeMillis() - startTime;
- logger.debug("Database health check: UP ({}ms)", responseTime);
- return true;
+ logger.debug("MySQL health check: UP ({}ms)", responseTime);
+
+ status.put("status", "UP");
+ details.put("responseTimeMs", responseTime);
+
+ // Get database version
+ String version = getMySQLVersion(connection);
+ if (version != null) {
+ details.put("version", version);
+ }
+
+ status.put("details", details);
+ return status;
}
}
}
}
- logger.warn("Database health check: Connection not valid");
- return false;
+ logger.warn("MySQL health check: Connection not valid");
+ status.put("status", "DOWN");
+ details.put("error", "Connection validation failed");
+ status.put("details", details);
+ return status;
} catch (Exception e) {
- logger.error("Database health check failed: {}", e.getMessage());
- return false;
+ logger.error("MySQL health check failed: {}", e.getMessage());
+ status.put("status", "DOWN");
+ details.put("error", e.getMessage());
+ details.put("errorType", e.getClass().getSimpleName());
+ status.put("details", details);
+ return status;
}
}
- private boolean checkRedisHealthInternal() {
+ private Map checkRedisHealth() {
+ Map status = new LinkedHashMap<>();
+ Map details = new LinkedHashMap<>();
long startTime = System.currentTimeMillis();
-
+
+ // Add connection details
+ details.put("type", "Redis");
+ details.put("host", redisHost);
+ details.put("port", redisPort);
+
try {
- String pong = redisTemplate.execute((RedisCallback) connection -> connection.ping());
-
+ // Use ping() from RedisConnection directly
+ String pong = redisTemplate.execute((RedisCallback) connection ->
+ connection.ping()
+ );
+
if ("PONG".equals(pong)) {
long responseTime = System.currentTimeMillis() - startTime;
logger.debug("Redis health check: UP ({}ms)", responseTime);
- return true;
+
+ status.put("status", "UP");
+ details.put("responseTimeMs", responseTime);
+
+ // Get Redis version
+ String version = getRedisVersion();
+ if (version != null) {
+ details.put("version", version);
+ }
+
+ status.put("details", details);
+ return status;
}
logger.warn("Redis health check: Ping returned unexpected response");
- return false;
+ status.put("status", "DOWN");
+ details.put("error", "Ping returned unexpected response");
+ status.put("details", details);
+ return status;
} catch (Exception e) {
logger.error("Redis health check failed: {}", e.getMessage());
- return false;
+ status.put("status", "DOWN");
+ details.put("error", e.getMessage());
+ details.put("errorType", e.getClass().getSimpleName());
+ status.put("details", details);
+ return status;
+ }
+ }
+
+ private Map checkMongoDBHealth() {
+ Map status = new LinkedHashMap<>();
+ Map details = new LinkedHashMap<>();
+ long startTime = System.currentTimeMillis();
+
+ // Add connection details
+ details.put("type", "MongoDB");
+ details.put("host", mongoHost);
+ details.put("port", mongoPort);
+ details.put("database", mongoDatabase);
+
+ try {
+ // Run ping command to check MongoDB connectivity
+ Document pingResult = mongoTemplate.getDb().runCommand(new Document("ping", 1));
+
+ if (pingResult != null && pingResult.getDouble("ok") == 1.0) {
+ long responseTime = System.currentTimeMillis() - startTime;
+ logger.debug("MongoDB health check: UP ({}ms)", responseTime);
+
+ status.put("status", "UP");
+ details.put("responseTimeMs", responseTime);
+
+ // Get MongoDB version
+ String version = getMongoDBVersion();
+ if (version != null) {
+ details.put("version", version);
+ }
+
+ status.put("details", details);
+ return status;
+ }
+ logger.warn("MongoDB health check: Ping returned unexpected response");
+ status.put("status", "DOWN");
+ details.put("error", "Ping returned unexpected response");
+ status.put("details", details);
+ return status;
+ } catch (Exception e) {
+ logger.error("MongoDB health check failed: {}", e.getMessage());
+ status.put("status", "DOWN");
+ details.put("error", e.getMessage());
+ details.put("errorType", e.getClass().getSimpleName());
+ status.put("details", details);
+ return status;
+ }
+ }
+
+ private String getMySQLVersion(Connection connection) {
+ try (PreparedStatement stmt = connection.prepareStatement(DB_VERSION_QUERY);
+ ResultSet rs = stmt.executeQuery()) {
+ if (rs.next()) {
+ return rs.getString(1);
+ }
+ } catch (Exception e) {
+ logger.debug("Could not retrieve MySQL version: {}", e.getMessage());
+ }
+ return null;
+ }
+
+ private String getRedisVersion() {
+ try {
+ Properties info = redisTemplate.execute((RedisCallback) connection ->
+ connection.serverCommands().info("server")
+ );
+ if (info != null && info.containsKey("redis_version")) {
+ return info.getProperty("redis_version");
+ }
+ } catch (Exception e) {
+ logger.debug("Could not retrieve Redis version: {}", e.getMessage());
+ }
+ return null;
+ }
+
+ private String getMongoDBVersion() {
+ try {
+ Document buildInfo = mongoTemplate.getDb().runCommand(new Document("buildInfo", 1));
+ if (buildInfo != null && buildInfo.containsKey("version")) {
+ return buildInfo.getString("version");
+ }
+ } catch (Exception e) {
+ logger.debug("Could not retrieve MongoDB version: {}", e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Extracts host from JDBC URL.
+ * Example: jdbc:mysql://mysql-container:3306/db_iemr -> mysql-container
+ */
+ private String extractHost(String jdbcUrl) {
+ if (jdbcUrl == null || "unknown".equals(jdbcUrl)) {
+ return "unknown";
+ }
+ try {
+ // Remove jdbc:mysql:// prefix
+ String withoutPrefix = jdbcUrl.replaceFirst("jdbc:mysql://", "");
+ // Get host:port part (before the first /)
+ int slashIndex = withoutPrefix.indexOf('/');
+ String hostPort = slashIndex > 0
+ ? withoutPrefix.substring(0, slashIndex)
+ : withoutPrefix;
+ // Get host (before the colon)
+ int colonIndex = hostPort.indexOf(':');
+ return colonIndex > 0 ? hostPort.substring(0, colonIndex) : hostPort;
+ } catch (Exception e) {
+ logger.debug("Could not extract host from URL: {}", e.getMessage());
+ }
+ return "unknown";
+ }
+
+ /**
+ * Extracts port from JDBC URL.
+ * Example: jdbc:mysql://mysql-container:3306/db_iemr -> 3306
+ */
+ private String extractPort(String jdbcUrl) {
+ if (jdbcUrl == null || "unknown".equals(jdbcUrl)) {
+ return "unknown";
+ }
+ try {
+ // Remove jdbc:mysql:// prefix
+ String withoutPrefix = jdbcUrl.replaceFirst("jdbc:mysql://", "");
+ // Get host:port part (before the first /)
+ int slashIndex = withoutPrefix.indexOf('/');
+ String hostPort = slashIndex > 0
+ ? withoutPrefix.substring(0, slashIndex)
+ : withoutPrefix;
+ // Get port (after the colon)
+ int colonIndex = hostPort.indexOf(':');
+ return colonIndex > 0 ? hostPort.substring(colonIndex + 1) : "3306";
+ } catch (Exception e) {
+ logger.debug("Could not extract port from URL: {}", e.getMessage());
+ }
+ return "3306";
+ }
+
+ /**
+ * Extracts database name from JDBC URL.
+ * Example: jdbc:mysql://mysql-container:3306/db_iemr?params -> db_iemr
+ */
+ private String extractDatabaseName(String jdbcUrl) {
+ if (jdbcUrl == null || "unknown".equals(jdbcUrl)) {
+ return "unknown";
+ }
+ try {
+ int lastSlash = jdbcUrl.lastIndexOf('/');
+ if (lastSlash >= 0 && lastSlash < jdbcUrl.length() - 1) {
+ String afterSlash = jdbcUrl.substring(lastSlash + 1);
+ int queryStart = afterSlash.indexOf('?');
+ if (queryStart > 0) {
+ return afterSlash.substring(0, queryStart);
+ }
+ return afterSlash;
+ }
+ } catch (Exception e) {
+ logger.debug("Could not extract database name: {}", e.getMessage());
}
+ return "unknown";
}
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index e65d5b3..bbc7f9e 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -74,3 +74,8 @@ swymed-edituser-url=swymed-base-url/SwymedWebApi/api/Contact
calibrationPageSize=5
biological-screening-device-url=http://localhost:8096/ezdx-hub-connect-srv
+
+## MongoDB Configuration
+spring.data.mongodb.host=mongodb-container
+spring.data.mongodb.port=27017
+spring.data.mongodb.database=amrit