-
Notifications
You must be signed in to change notification settings - Fork 30
Implement /health and /version endpoints for Admin API #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds health and version services/endpoints, a security filter registration, updates JWT filter to skip health/version endpoints, and updates Maven plugins to emit git/build metadata for runtime consumption. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API
participant JwtFilter as JwtUserIdValidationFilter
participant HealthCtrl as HealthController
participant HealthSvc as HealthService
participant VersionCtrl as VersionController
participant VersionSvc as VersionService
Client->>API: GET /health
API->>JwtFilter: filter request
JwtFilter-->>API: skip validation for /health
API->>HealthCtrl: health()
HealthCtrl->>HealthSvc: checkHealth()
HealthSvc-->>HealthCtrl: health map
HealthCtrl-->>API: 200/503 JSON
API-->>Client: response
Client->>API: GET /version
API->>JwtFilter: filter request
JwtFilter-->>API: skip validation for /version
API->>VersionCtrl: versionInformation()
VersionCtrl->>VersionSvc: getVersionInformation()
VersionSvc-->>VersionCtrl: JSON string
VersionCtrl-->>API: 200 JSON
API-->>Client: response
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~18 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🔭 Outside diff range comments (1)
src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java (1)
165-170
: Remove duplicate codeLines 168-169 are exact duplicates of lines 165-166.
logger.warn("No valid authentication token found"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Invalid or missing token"); - - logger.warn("No valid authentication token found"); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Invalid or missing token");
🧹 Nitpick comments (1)
src/main/java/com/iemr/admin/controller/health/HealthController.java (1)
31-40
: Consider adding OpenAPI documentationFor consistency with other endpoints like
/version
, consider adding Swagger/OpenAPI documentation.+import io.swagger.v3.oas.annotations.Operation; + @RestController public class HealthController { + @Operation(summary = "Health check endpoint") @GetMapping("/health") public ResponseEntity<Map<String, String>> health() {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
pom.xml
(1 hunks)src/main/java/com/iemr/admin/config/SecurityFilterConfig.java
(1 hunks)src/main/java/com/iemr/admin/controller/health/HealthController.java
(1 hunks)src/main/java/com/iemr/admin/controller/version/VersionController.java
(2 hunks)src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java
(4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/main/java/com/iemr/admin/controller/health/HealthController.java (1)
src/main/java/com/iemr/admin/controller/version/VersionController.java (1)
RestController
(43-160)
src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java (1)
src/main/java/com/iemr/admin/utils/http/AuthorizationHeaderRequestWrapper.java (1)
AuthorizationHeaderRequestWrapper
(10-42)
🔇 Additional comments (6)
pom.xml (1)
489-500
: LGTM!The addition of the
build-info
goal correctly generates build metadata that the/version
endpoint can consume.src/main/java/com/iemr/admin/config/SecurityFilterConfig.java (1)
12-32
: LGTM!Clean Spring configuration that properly registers the JWT filter with URL exclusions.
src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java (2)
24-48
: LGTM!Proper Spring integration with no-args constructor and FilterConfig storage.
224-236
: LGTM!Proper Spring setter injection with appropriate null handling.
src/main/java/com/iemr/admin/controller/version/VersionController.java (2)
48-64
: LGTM!Proper JSON content type declaration and structured response handling.
137-160
: LGTM!Well-implemented helper methods with proper resource management and error handling.
private String readGitPropertiesAsJson() throws Exception { | ||
StringBuilder json = new StringBuilder(); | ||
json.append("{\n"); | ||
|
||
// Read Git properties | ||
Properties gitProps = loadPropertiesFile("git.properties"); | ||
if (gitProps != null) { | ||
// For git.commit.id, look for both standard and abbrev versions | ||
String commitId = gitProps.getProperty("git.commit.id", null); | ||
if (commitId == null) { | ||
commitId = gitProps.getProperty("git.commit.id.abbrev", "unknown"); | ||
} | ||
json.append(" \"git.commit.id\": \"").append(commitId).append("\",\n"); | ||
|
||
// For git.build.time, look for various possible property names | ||
String buildTime = gitProps.getProperty("git.build.time", null); | ||
if (buildTime == null) { | ||
buildTime = gitProps.getProperty("git.commit.time", null); | ||
} | ||
if (buildTime == null) { | ||
buildTime = gitProps.getProperty("git.commit.timestamp", "unknown"); | ||
} | ||
json.append(" \"git.build.time\": \"").append(buildTime).append("\",\n"); | ||
} else { | ||
logger.warn("git.properties file not found. Git information will be unavailable."); | ||
json.append(" \"git.commit.id\": \"information unavailable\",\n"); | ||
json.append(" \"git.build.time\": \"information unavailable\",\n"); | ||
} | ||
|
||
// Read build properties if available | ||
Properties buildProps = loadPropertiesFile("META-INF/build-info.properties"); | ||
if (buildProps != null) { | ||
// Extract version - checking for both standard and nested formats | ||
String version = buildProps.getProperty("build.version", null); | ||
if (version == null) { | ||
version = buildProps.getProperty("build.version.number", null); | ||
} | ||
if (version == null) { | ||
version = buildProps.getProperty("version", "unknown"); | ||
} | ||
json.append(" \"build.version\": \"").append(version).append("\",\n"); | ||
|
||
// Extract time - checking for both standard and alternate formats | ||
String time = buildProps.getProperty("build.time", null); | ||
if (time == null) { | ||
time = buildProps.getProperty("build.timestamp", null); | ||
} | ||
if (time == null) { | ||
time = buildProps.getProperty("timestamp", "unknown"); | ||
} | ||
json.append(" \"build.time\": \"").append(time).append("\",\n"); | ||
} else { | ||
logger.info("build-info.properties not found, trying Maven properties"); | ||
// Fallback to maven project version | ||
Properties mavenProps = loadPropertiesFile("META-INF/maven/com.iemr.admin/admin-api/pom.properties"); | ||
if (mavenProps != null) { | ||
String version = mavenProps.getProperty("version", "unknown"); | ||
json.append(" \"build.version\": \"").append(version).append("\",\n"); | ||
json.append(" \"build.time\": \"").append(getCurrentIstTimeFormatted()).append("\",\n"); | ||
} else { | ||
logger.warn("Neither build-info.properties nor Maven properties found."); | ||
json.append(" \"build.version\": \"3.1.0\",\n"); // Default version | ||
json.append(" \"build.time\": \"").append(getCurrentIstTimeFormatted()).append("\",\n"); | ||
} | ||
} | ||
json.append(" \"current.time\": \"").append(getCurrentIstTimeFormatted()).append("\"\n"); | ||
|
||
json.append(" }"); | ||
return json.toString(); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider using proper JSON serialization
Manual JSON construction with StringBuilder is error-prone. Property values containing special characters (quotes, newlines) will break the JSON format.
Consider using Jackson (already in classpath) or creating a POJO:
private String readGitPropertiesAsJson() throws Exception {
Map<String, String> versionInfo = new LinkedHashMap<>();
// Read Git properties
Properties gitProps = loadPropertiesFile("git.properties");
if (gitProps != null) {
String commitId = gitProps.getProperty("git.commit.id",
gitProps.getProperty("git.commit.id.abbrev", "unknown"));
versionInfo.put("git.commit.id", commitId);
String buildTime = gitProps.getProperty("git.build.time",
gitProps.getProperty("git.commit.time",
gitProps.getProperty("git.commit.timestamp", "unknown")));
versionInfo.put("git.build.time", buildTime);
} else {
logger.warn("git.properties file not found. Git information will be unavailable.");
versionInfo.put("git.commit.id", "information unavailable");
versionInfo.put("git.build.time", "information unavailable");
}
// ... similar for other properties ...
versionInfo.put("current.time", getCurrentIstTimeFormatted());
// Use ObjectMapper to properly serialize
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(versionInfo);
}
🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/controller/version/VersionController.java lines
66 to 136, the method readGitPropertiesAsJson manually builds a JSON string
using StringBuilder, which risks breaking JSON format if property values contain
special characters. To fix this, replace the manual string construction with a
Map to hold the properties and use Jackson's ObjectMapper to serialize the Map
to a JSON string. This ensures proper escaping and formatting of all values.
Populate the Map with all required properties as before, then return the JSON
string by calling ObjectMapper.writeValueAsString on the Map.
// Check for excluded URLs first | ||
String path = request.getRequestURI(); | ||
if (isExcludedUrl(path) || | ||
path.equals("/health") || | ||
path.equals("/version")) { | ||
filterChain.doFilter(servletRequest, servletResponse); | ||
return; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove hardcoded URL exclusions
The /health
and /version
paths are already configured as excluded URLs in SecurityFilterConfig
. Remove the hardcoded checks to follow DRY principle.
// Check for excluded URLs first
String path = request.getRequestURI();
- if (isExcludedUrl(path) ||
- path.equals("/health") ||
- path.equals("/version")) {
+ if (isExcludedUrl(path)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// Check for excluded URLs first | |
String path = request.getRequestURI(); | |
if (isExcludedUrl(path) || | |
path.equals("/health") || | |
path.equals("/version")) { | |
filterChain.doFilter(servletRequest, servletResponse); | |
return; | |
} | |
// Check for excluded URLs first | |
String path = request.getRequestURI(); | |
if (isExcludedUrl(path)) { | |
filterChain.doFilter(servletRequest, servletResponse); | |
return; | |
} |
🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java between
lines 74 and 82, remove the hardcoded checks for "/health" and "/version" URL
paths since these are already included in the excluded URLs configured in
SecurityFilterConfig. Keep only the call to isExcludedUrl(path) to check for
exclusions, ensuring no duplication and adherence to the DRY principle.
@GetMapping("/health") | ||
public ResponseEntity<Map<String, String>> health() { | ||
Map<String, String> response = new HashMap<>(); | ||
response.put("status", "UP"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This alone won't be enough.
Can we test associated services like DB and Redis connection?
@vishwab1 / @vanitha1822 |
json.append(" \"build.time\": \"").append(getCurrentIstTimeFormatted()).append("\",\n"); | ||
} else { | ||
logger.warn("Neither build-info.properties nor Maven properties found."); | ||
json.append(" \"build.version\": \"3.1.0\",\n"); // Default version |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid Hardcoding Version
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can move the logic (like reading Git properties and building the JSON) into a separate service method instead of writing everything inside the controller. Also, let’s review all the suggestions given by CodeRabbit once again. One of the points to check is the use of StringBuilder, as it might cause issues in some cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (3)
src/main/java/com/iemr/admin/service/version/VersionService.java (2)
45-46
: Consider using @ConfigurationProperties for better configuration management.Using
@Value
for individual properties works but@ConfigurationProperties
would provide better type safety and validation for application properties.
107-107
: Hardcoded Maven path may not be portable across different projects.The hardcoded path
META-INF/maven/com.iemr.admin/admin-api/pom.properties
is specific to this project's group/artifact ID and may break if the project structure changes.Consider making this configurable or using a more generic approach:
- Properties mavenProps = loadPropertiesFile("META-INF/maven/com.iemr.admin/admin-api/pom.properties"); + Properties mavenProps = loadPropertiesFile("META-INF/maven/" + + System.getProperty("project.groupId", "com.iemr.admin") + "/" + + System.getProperty("project.artifactId", "admin-api") + "/pom.properties");src/main/java/com/iemr/admin/controller/health/HealthController.java (1)
54-55
: Consider using constants for status values.The string literal "UP" is used for comparison. Consider defining constants to avoid magic strings and ensure consistency across the codebase.
+ private static final String STATUS_UP = "UP"; + private static final String STATUS_DOWN = "DOWN"; + // Return 503 if any service is down, 200 if all are up String status = (String) healthStatus.get("status"); - HttpStatus httpStatus = "UP".equals(status) ? HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE; + HttpStatus httpStatus = STATUS_UP.equals(status) ? HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
pom.xml
(3 hunks)src/main/java/com/iemr/admin/config/SecurityFilterConfig.java
(1 hunks)src/main/java/com/iemr/admin/controller/health/HealthController.java
(1 hunks)src/main/java/com/iemr/admin/controller/version/VersionController.java
(1 hunks)src/main/java/com/iemr/admin/service/health/HealthService.java
(1 hunks)src/main/java/com/iemr/admin/service/version/VersionService.java
(1 hunks)src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java
(3 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/main/java/com/iemr/admin/utils/JwtUserIdValidationFilter.java
🚧 Files skipped from review as they are similar to previous changes (3)
- src/main/java/com/iemr/admin/config/SecurityFilterConfig.java
- src/main/java/com/iemr/admin/controller/version/VersionController.java
- pom.xml
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/main/java/com/iemr/admin/controller/health/HealthController.java (1)
src/main/java/com/iemr/admin/controller/version/VersionController.java (1)
RestController
(37-60)
src/main/java/com/iemr/admin/service/version/VersionService.java (1)
src/main/java/com/iemr/admin/service/health/HealthService.java (1)
Service
(39-198)
🔇 Additional comments (8)
src/main/java/com/iemr/admin/service/version/VersionService.java (4)
50-58
: LGTM!Good error handling with graceful fallback to error response when version information cannot be built.
75-91
: Robust fallback logic for Git information.The multiple fallback property keys ensure compatibility with different git-commit-id-plugin configurations, and the graceful handling of missing files with warning logs is well-implemented.
126-138
: Proper resource management with try-with-resources.Good use of try-with-resources for automatic resource cleanup and appropriate exception handling with logging.
140-153
: Comprehensive error response handling.The nested try-catch ensures that even if JSON serialization of the error response fails, a basic JSON error message is returned. This prevents the endpoint from returning non-JSON responses.
src/main/java/com/iemr/admin/controller/health/HealthController.java (1)
46-59
: Addresses previous review feedback with comprehensive health checks.The implementation now includes database and Redis connection testing through the
HealthService
, directly addressing the previous comment about testing associated services. The HTTP status code mapping (200 for UP, 503 for DOWN) follows health check best practices.src/main/java/com/iemr/admin/service/health/HealthService.java (3)
87-134
: Comprehensive database health check implementation.Excellent implementation that tests connection validity, executes a simple query, collects metadata, measures response time, and provides detailed error information. The try-with-resources ensures proper connection cleanup.
147-153
: Redis operations test provides thorough validation.The set/get/delete test ensures Redis is not only reachable but also functional for actual operations. Good cleanup of test data to avoid pollution.
189-197
: Proper security practice with URL sanitization.Good security implementation to prevent password exposure in logs and responses by sanitizing database URLs.
@Autowired(required = false) | ||
private RedisTemplate<String, Object> redisTemplate; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Verify RedisTemplate configuration and error handling.
Ensure that the RedisTemplate<String, Object>
bean is properly configured in the Spring context and that connection pool settings are appropriate for health checks.
🏁 Script executed:
#!/bin/bash
# Description: Check for RedisTemplate configuration and usage patterns
# Search for RedisTemplate configuration
ast-grep --pattern $'@Bean
$$$
RedisTemplate$_($$$) {
$$$
}'
# Search for Redis configuration files
fd -e properties -e yml -e yaml | xargs grep -l -i redis
Length of output: 296
I’d like to confirm whether Spring Boot’s Redis auto-configuration is on the classpath (i.e. that the spring-boot-starter-data-redis
dependency is present and will supply a RedisTemplate
bean). Checking your Maven config next:
🏁 Script executed:
#!/bin/bash
# Check for Spring Data Redis starter dependency
grep -R "spring-boot-starter-data-redis" -n .
Length of output: 118
Let’s verify how redisTemplate
is used in your health check and confirm your Redis settings:
🏁 Script executed:
#!/bin/bash
# 1. Show redisTemplate usage in HealthService
grep -R "redisTemplate" -n -A3 -B3 src/main/java/com/iemr/admin/service/health/HealthService.java
# 2. List all spring.redis properties in your env and resource files
grep -R "spring.redis" -n src/main/environment src/main/resources/application.properties
# 3. Check for any custom RedisConnectionFactory beans
grep -R "RedisConnectionFactory" -n src/main/java
Length of output: 2553
Let’s confirm the actual @Bean
definitions for your Redis templates and check if any connection‐pool settings are configured:
🏁 Script executed:
#!/bin/bash
# 1. Inspect @Bean methods in RoleMasterApplication and RedisConfig
grep -R "@Bean" -n -A3 -B3 src/main/java/com/iemr/admin/RoleMasterApplication.java src/main/java/com/iemr/admin/config/RedisConfig.java
# 2. Look for Lettuce/Jedis pool properties
grep -R "spring.redis.*pool" -n src/main/environment src/main/resources/application.properties
Length of output: 2890
Action Required: Disambiguate RedisTemplate
beans & configure connection pooling
It looks like you’ve defined two @Bean
methods both named redisTemplate
(one in RoleMasterApplication, one in RedisConfig), and you haven’t added any pool settings for Lettuce/Jedis. To avoid startup errors and ensure robust health checks, please:
- Rename or qualify your template beans so only the intended
RedisTemplate<String,Object>
is injected intoHealthService
. - Add connection‐pool properties (e.g.
spring.redis.lettuce.pool.max-active
,max-idle
,max-wait
or equivalent Jedis settings) in yourapplication*.properties
or environment files. - Enhance
checkRedisHealth()
to catch and report any Redis exceptions (e.g. connection timeouts, IO errors) as service DOWN.
🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/service/health/HealthService.java around lines
48-49, the RedisTemplate injection is ambiguous due to multiple beans named
redisTemplate. To fix this, use @Qualifier to specify which RedisTemplate bean
to inject or rename the beans to have distinct names. Additionally, configure
connection pooling properties for Lettuce or Jedis in your application
properties files to improve connection management. Finally, update the
checkRedisHealth() method to catch Redis-related exceptions and mark the service
as DOWN when such exceptions occur.
Mostly LGTM. cc: @vishwab1 |
dbStatus.put("status", "UP"); | ||
dbStatus.put("database", connection.getMetaData().getDatabaseProductName()); | ||
dbStatus.put("driver", connection.getMetaData().getDriverName()); | ||
dbStatus.put("url", sanitizeUrl(connection.getMetaData().getURL())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to expose this to public.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (1)
src/main/java/com/iemr/admin/service/health/HealthService.java (1)
53-55
: Fix ambiguous RedisTemplate injection; prefer StringRedisTemplate to avoid NoUniqueBeanDefinitionExceptionWith multiple RedisTemplate beans in the context, field autowiring by type remains ambiguous even with required=false. Inject StringRedisTemplate (auto-configured and unique) to eliminate ambiguity and align with String value operations used in your check.
Apply this diff:
- @Autowired(required = false) - private RedisTemplate<String, Object> redisTemplate; + @Autowired(required = false) + private StringRedisTemplate stringRedisTemplate;And add the missing import (see import diff below).
🧹 Nitpick comments (5)
src/main/java/com/iemr/admin/service/health/HealthService.java (5)
24-31
: Optional: import Duration instead of using FQCNOnly needed if you adopt the TTL change above.
import java.sql.PreparedStatement; import java.sql.ResultSet; import java.time.Instant; +import java.time.Duration; import java.util.HashMap; import java.util.Map;
88-89
: Reduce log verbosity for frequently-polled endpointHealth endpoints are often polled. Logging at INFO on every call can flood logs. Switch to DEBUG.
- logger.info("Health check completed - Overall status: {}", overallHealth ? "UP" : "DOWN"); + logger.debug("Health check completed - Overall status: {}", overallHealth ? "UP" : "DOWN");
56-90
: Separate liveness and readiness; avoid exposing detailed internals on public /healthCurrent payload includes DB/Redis details. PR objective mentions a lightweight “status: UP” health endpoint; also, a previous comment said “We don't need to expose this to public.” Consider:
- Keep /health minimal (liveness): only status and timestamp.
- Provide /health/ready or a secured endpoint for detailed checks (DB/Redis).
- Or gate inclusion of the "services" map behind a property.
If you want a quick toggle via property, apply:
@Value("${app.version:unknown}") private String appVersion; + @Value("${health.details.enabled:false}") + private boolean healthDetailsEnabled;and:
- healthStatus.put("services", services); + if (healthDetailsEnabled) { + healthStatus.put("services", services); + }Please confirm whether /health is intended to be publicly accessible and if detailed service statuses should be suppressed by default. I can follow up with a small controller change to split endpoints if needed.
41-46
: Consider leveraging Spring Boot Actuator HealthIndicatorsSpring Boot Actuator provides production-grade /actuator/health with DB and Redis indicators, standardized status codes, and config-driven detail toggles. Migrating reduces custom code and maintenance.
If Actuator is already on the classpath, you can:
- Expose health endpoint and include Redis/DB indicators via properties.
- Optionally add a custom HealthIndicator if extra checks are still needed.
47-55
: Prefer constructor injection over field injectionConstructor injection improves immutability, makes dependencies explicit, and simplifies testing.
I can provide a small refactor to constructor-based injection if you’d like.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/main/java/com/iemr/admin/service/health/HealthService.java
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/com/iemr/admin/service/health/HealthService.java (1)
src/main/java/com/iemr/admin/service/version/VersionService.java (1)
Service
(40-154)
🔇 Additional comments (2)
src/main/java/com/iemr/admin/service/health/HealthService.java (2)
142-146
: Use StringRedisTemplate for ping executionEnsure consistency with the injected bean and avoid serializer surprises.
- String pong = redisTemplate.execute((RedisCallback<String>) connection -> { - return connection.ping(); - }); + String pong = stringRedisTemplate.execute((RedisCallback<String>) connection -> connection.ping());Likely an incorrect or invalid review comment.
37-39
: Add missing import for StringRedisTemplateRequired for the suggested change to inject the dedicated string template.
import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service;
Likely an incorrect or invalid review comment.
if (redisTemplate != null) { | ||
Map<String, Object> redisStatus = checkRedisHealth(); | ||
services.put("redis", redisStatus); | ||
if (!"UP".equals(redisStatus.get("status"))) { | ||
overallHealth = false; | ||
} | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Update null-check to use StringRedisTemplate
Continue the variable rename to the string template to avoid NPEs and mismatches.
- if (redisTemplate != null) {
+ if (stringRedisTemplate != null) {
Map<String, Object> redisStatus = checkRedisHealth();
services.put("redis", redisStatus);
if (!"UP".equals(redisStatus.get("status"))) {
overallHealth = false;
}
} else {
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/service/health/HealthService.java around lines
69 to 75, the null-check and subsequent usage still reference redisTemplate
instead of the renamed stringRedisTemplate which can cause NPEs/mismatches;
update the conditional and any calls to use stringRedisTemplate (e.g., if
(stringRedisTemplate != null) { Map<String,Object> redisStatus =
checkRedisHealth(); ... }) and ensure checkRedisHealth and related
methods/fields accept and use stringRedisTemplate consistently.
// Test connection validity | ||
boolean isConnectionValid = connection.isValid(5); // 5 second timeout | ||
|
||
if (isConnectionValid) { | ||
// Execute a simple query to ensure database is responsive | ||
try (PreparedStatement stmt = connection.prepareStatement(DB_HEALTH_CHECK_QUERY); | ||
ResultSet rs = stmt.executeQuery()) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Shorten DB isValid timeout and add JDBC query timeout
Prevent the health check from hanging on slow DBs and bound the query execution time.
- boolean isConnectionValid = connection.isValid(5); // 5 second timeout
+ boolean isConnectionValid = connection.isValid(2); // 2 second timeout
if (isConnectionValid) {
// Execute a simple query to ensure database is responsive
- try (PreparedStatement stmt = connection.prepareStatement(DB_HEALTH_CHECK_QUERY);
- ResultSet rs = stmt.executeQuery()) {
+ try (PreparedStatement stmt = connection.prepareStatement(DB_HEALTH_CHECK_QUERY)) {
+ stmt.setQueryTimeout(3); // seconds
+ try (ResultSet rs = stmt.executeQuery()) {
if (rs.next() && rs.getInt(1) == 1) {
long responseTime = System.currentTimeMillis() - startTime;
dbStatus.put("status", "UP");
dbStatus.put("responseTime", responseTime + "ms");
dbStatus.put("message", "Database connection successful");
logger.debug("Database health check: UP ({}ms)", responseTime);
} else {
dbStatus.put("status", "DOWN");
dbStatus.put("message", "Database query returned unexpected result");
logger.warn("Database health check: Query returned unexpected result");
}
- }
+ }
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// Test connection validity | |
boolean isConnectionValid = connection.isValid(5); // 5 second timeout | |
if (isConnectionValid) { | |
// Execute a simple query to ensure database is responsive | |
try (PreparedStatement stmt = connection.prepareStatement(DB_HEALTH_CHECK_QUERY); | |
ResultSet rs = stmt.executeQuery()) { | |
// Test connection validity | |
boolean isConnectionValid = connection.isValid(2); // 2 second timeout | |
if (isConnectionValid) { | |
// Execute a simple query to ensure database is responsive | |
try (PreparedStatement stmt = connection.prepareStatement(DB_HEALTH_CHECK_QUERY)) { | |
stmt.setQueryTimeout(3); // seconds | |
try (ResultSet rs = stmt.executeQuery()) { | |
if (rs.next() && rs.getInt(1) == 1) { | |
long responseTime = System.currentTimeMillis() - startTime; | |
dbStatus.put("status", "UP"); | |
dbStatus.put("responseTime", responseTime + "ms"); | |
dbStatus.put("message", "Database connection successful"); | |
logger.debug("Database health check: UP ({}ms)", responseTime); | |
} else { | |
dbStatus.put("status", "DOWN"); | |
dbStatus.put("message", "Database query returned unexpected result"); | |
logger.warn("Database health check: Query returned unexpected result"); | |
} | |
} | |
} |
🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/service/health/HealthService.java around lines
97 to 104, shorten the connection.isValid timeout (e.g., from 5 to 1 second) and
set a JDBC statement query timeout on the PreparedStatement before executing
(e.g., stmt.setQueryTimeout(2)); ensure you catch and handle
SQLTimeoutException/SQLException from the query so the health check fails fast
and resources are still closed.
// Additional test: set and get a test key | ||
String testKey = "health:check:" + System.currentTimeMillis(); | ||
String testValue = "test-value"; | ||
|
||
redisTemplate.opsForValue().set(testKey, testValue); | ||
Object retrievedValue = redisTemplate.opsForValue().get(testKey); | ||
redisTemplate.delete(testKey); // Clean up test key | ||
|
||
if (testValue.equals(retrievedValue)) { | ||
redisStatus.put("status", "UP"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Fix Redis value serialization mismatch; set TTL to avoid lingering keys on failure
When using RedisTemplate<String, Object>, retrievedValue may not be a String (e.g., JDK-serialized), causing equality to fail. Using StringRedisTemplate ensures String-in/String-out. Also, add a short TTL to prevent orphaned keys if delete is skipped due to an exception.
- // Additional test: set and get a test key
+ // Additional test: set and get a test key
String testKey = "health:check:" + System.currentTimeMillis();
String testValue = "test-value";
- redisTemplate.opsForValue().set(testKey, testValue);
- Object retrievedValue = redisTemplate.opsForValue().get(testKey);
- redisTemplate.delete(testKey); // Clean up test key
+ stringRedisTemplate.opsForValue().set(testKey, testValue, java.time.Duration.ofSeconds(5));
+ String retrievedValue = stringRedisTemplate.opsForValue().get(testKey);
+ stringRedisTemplate.delete(testKey); // Clean up test key
- if (testValue.equals(retrievedValue)) {
+ if (testValue.equals(retrievedValue)) {
Note: If you prefer an import over FQCN, add import java.time.Duration;
(see import diff suggestion).
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/service/health/HealthService.java around lines
150 to 159, the health-check writes and reads a test key using
RedisTemplate<String, Object>, which can return a non-String (JDK-serialized)
value and break equality; replace or cast to a String-safe API by using
StringRedisTemplate (or ensure the template is configured for String
serialization) so retrievedValue is a String, set the test key with a short TTL
(e.g., Duration.ofSeconds(5)) to avoid orphaned keys if delete fails, and ensure
the delete is performed in a finally block or guarded catch so cleanup is
attempted even on exceptions; add import java.time.Duration if you switch to
using Duration.
} catch (Exception e) { | ||
long responseTime = System.currentTimeMillis() - startTime; | ||
redisStatus.put("status", "DOWN"); | ||
redisStatus.put("message", "Redis connection failed: " + e.getMessage()); | ||
redisStatus.put("responseTime", responseTime + "ms"); | ||
redisStatus.put("error", e.getClass().getSimpleName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid leaking exception messages in public health payload
Returning raw exception messages can leak internal hostnames, credentials, or topology. Keep the message generic and log details server-side.
- redisStatus.put("message", "Redis connection failed: " + e.getMessage());
+ redisStatus.put("message", "Redis connection failed");
redisStatus.put("responseTime", responseTime + "ms");
redisStatus.put("error", e.getClass().getSimpleName());
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
} catch (Exception e) { | |
long responseTime = System.currentTimeMillis() - startTime; | |
redisStatus.put("status", "DOWN"); | |
redisStatus.put("message", "Redis connection failed: " + e.getMessage()); | |
redisStatus.put("responseTime", responseTime + "ms"); | |
redisStatus.put("error", e.getClass().getSimpleName()); | |
} catch (Exception e) { | |
long responseTime = System.currentTimeMillis() - startTime; | |
redisStatus.put("status", "DOWN"); | |
redisStatus.put("message", "Redis connection failed"); | |
redisStatus.put("responseTime", responseTime + "ms"); | |
redisStatus.put("error", e.getClass().getSimpleName()); |
🤖 Prompt for AI Agents
In src/main/java/com/iemr/admin/service/health/HealthService.java around lines
176 to 181, replace the health payload's inclusion of the raw exception message
with a generic message (e.g., "Redis connection failed") and keep error type
minimal (or omit), and move the full exception details into a server-side log
statement (e.g., logger.error with the exception) so the health endpoint does
not expose internal hostnames/credentials; ensure responseTime remains but do
not include e.getMessage() in the returned payload.
📋 Description
JIRA ID:
This PR resolve issue
This PR implements both the
/health
and/version
endpoints for the Admin API service as requested in issue #102What has been implemented:
/health
Endpoint:/health
endpoint that returns a minimal response with status "UP"/version
Endpoint:/version
endpoint that returns:git.commit.id
)git.build.time
)build.version
)build.time
)✅ Type of Change
ℹ️ Additional Information
Tested in
Docker environment
Summary by CodeRabbit
New Features
Refactor
Chores