From f286fadc516e94d7ea81ff5793baaeabb385b50f Mon Sep 17 00:00:00 2001 From: PragyaTripathi990 <16pragyatripathi@gmail.com> Date: Sun, 10 May 2026 18:27:55 +0530 Subject: [PATCH] fix(hwc-api): return correct HTTP status codes and remove PII from logs in GeneralOPD Addresses issues #153 and #115. Problems fixed: - All 8 GeneralOPDController methods returned String (always HTTP 200), even on error. Clients could not distinguish success from failure via HTTP semantics. - Every controller method logged the full raw requestObj at INFO level, exposing patient vitals, history, prescriptions, and examination details in production logs. - OutputResponse.BAD_REQUEST was set to 404 (Not Found) instead of 400. - SWYMED_EXCEPTION and TM_EXCEPTION both used error code 5010, making them indistinguishable in logs and client error handling. Changes: - Refactor all 8 methods to return ResponseEntity using the existing toStringWithHttpStatus() method which was already implemented but unused. - Extend toStringWithHttpStatus() to map USERID/PASSWORD failures to 401, PRIVILEGE_FAILURE to 403, and parsing/object errors to 400, so clients receive semantically correct status codes. - Fix BAD_REQUEST constant from 404 to 400. - Fix TM_EXCEPTION constant from 5010 to 5011 to distinguish it from SWYMED_EXCEPTION. - Remove all logger.info() calls that concatenated raw requestObj (patient data). Error-level logging keeps the exception message only, not request payloads. - Add GlobalExceptionHandler (@RestControllerAdvice) to catch any exception that escapes individual controller try-catch blocks, preventing unstructured 500s. Co-Authored-By: Claude Sonnet 4.6 --- .../controller/GlobalExceptionHandler.java | 57 ++++++ .../generalOPD/GeneralOPDController.java | 181 +++++------------- .../hwc/utils/response/OutputResponse.java | 20 +- 3 files changed, 124 insertions(+), 134 deletions(-) create mode 100644 src/main/java/com/iemr/hwc/controller/GlobalExceptionHandler.java diff --git a/src/main/java/com/iemr/hwc/controller/GlobalExceptionHandler.java b/src/main/java/com/iemr/hwc/controller/GlobalExceptionHandler.java new file mode 100644 index 00000000..a5936cbc --- /dev/null +++ b/src/main/java/com/iemr/hwc/controller/GlobalExceptionHandler.java @@ -0,0 +1,57 @@ +/* +* 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.hwc.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import com.iemr.hwc.utils.exception.IEMRException; +import com.iemr.hwc.utils.response.OutputResponse; + +/** + * Catches any exception that escapes individual controller try-catch blocks, + * ensuring the API never returns an unstructured 500 or a misleading 200. + */ +@RestControllerAdvice +public class GlobalExceptionHandler { + + private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + @ExceptionHandler(IEMRException.class) + public ResponseEntity handleIEMRException(IEMRException e) { + logger.error("IEMRException: {}", e.getMessage()); + OutputResponse response = new OutputResponse(); + response.setError(OutputResponse.USERID_FAILURE, e.getMessage()); + return response.toStringWithHttpStatus(); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGenericException(Exception e) { + logger.error("Unhandled exception: {}", e.getMessage(), e); + OutputResponse response = new OutputResponse(); + response.setError(OutputResponse.GENERIC_FAILURE, "An unexpected error occurred"); + return response.toStringWithHttpStatus(); + } +} diff --git a/src/main/java/com/iemr/hwc/controller/generalOPD/GeneralOPDController.java b/src/main/java/com/iemr/hwc/controller/generalOPD/GeneralOPDController.java index 44d88537..37075e7d 100644 --- a/src/main/java/com/iemr/hwc/controller/generalOPD/GeneralOPDController.java +++ b/src/main/java/com/iemr/hwc/controller/generalOPD/GeneralOPDController.java @@ -31,6 +31,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; @@ -80,7 +81,7 @@ public void setGeneralOPDServiceImpl(GeneralOPDServiceImpl generalOPDServiceImpl */ @Operation(summary = "Save general OPD data collected by nurse") @PostMapping(value = { "/save/nurseData" }) - public String saveBenGenOPDNurseData(@RequestBody String requestObj, + public ResponseEntity saveBenGenOPDNurseData(@RequestBody String requestObj, @RequestHeader(value = "Authorization") String Authorization) throws Exception { OutputResponse response = new OutputResponse(); @@ -91,21 +92,19 @@ public String saveBenGenOPDNurseData(@RequestBody String requestObj, jsnOBJ = jsnElmnt.getAsJsonObject(); try { - logger.info("Request object for GeneralOPD nurse data saving :" + requestObj); - if (jsnOBJ != null) { String genOPDRes = generalOPDServiceImpl.saveNurseData(jsnOBJ, Authorization); response.setResponse(genOPDRes); } else { - response.setResponse("Invalid request"); + response.setError(OutputResponse.BAD_REQUEST, "Invalid request"); } } catch (Exception e) { - logger.error("Error in nurse data saving :" + e.getMessage()); + logger.error("Error in nurse data saving: {}", e.getMessage()); generalOPDServiceImpl.deleteVisitDetails(jsnOBJ); response.setError(5000, e.getMessage()); } } - return response.toString(); + return response.toStringWithHttpStatus(); } /** @@ -115,12 +114,10 @@ public String saveBenGenOPDNurseData(@RequestBody String requestObj, */ @Operation(summary = "Save general OPD data collected by doctor") @PostMapping(value = { "/save/doctorData" }) - public String saveBenGenOPDDoctorData(@RequestBody String requestObj, + public ResponseEntity saveBenGenOPDDoctorData(@RequestBody String requestObj, @RequestHeader(value = "Authorization") String Authorization) { OutputResponse response = new OutputResponse(); try { - logger.info("Request object for GeneralOPD doctor data saving :" + requestObj); - JsonObject jsnOBJ = new JsonObject(); JsonParser jsnParser = new JsonParser(); JsonElement jsnElmnt = jsnParser.parse(requestObj); @@ -129,7 +126,6 @@ public String saveBenGenOPDDoctorData(@RequestBody String requestObj, if (jsnOBJ != null) { Long genOPDRes = generalOPDServiceImpl.saveDoctorData(jsnOBJ, Authorization); if (null != genOPDRes && genOPDRes > 0) { - // Extract drug IDs from JsonObject List prescribedDrugIDs = new ArrayList<>(); if (jsnOBJ.has("savedDrugIDs") && !jsnOBJ.get("savedDrugIDs").isJsonNull()) { JsonArray drugIDsArray = jsnOBJ.getAsJsonArray("savedDrugIDs"); @@ -137,27 +133,21 @@ public String saveBenGenOPDDoctorData(@RequestBody String requestObj, prescribedDrugIDs.add(drugIDsArray.get(j).getAsLong()); } } - - // Create response with message and IDs Map responseData = new HashMap<>(); responseData.put("message", "Data saved successfully"); responseData.put("prescribedDrugIDs", prescribedDrugIDs); - - Gson gson = new Gson(); - String responseJson = gson.toJson(responseData); - response.setResponse(responseJson); + response.setResponse(new Gson().toJson(responseData)); } else { - response.setResponse("Unable to save data"); + response.setError(OutputResponse.GENERIC_FAILURE, "Unable to save data"); } - } else { - response.setResponse("Invalid request"); + response.setError(OutputResponse.BAD_REQUEST, "Invalid request"); } } catch (Exception e) { - logger.error("Error in doctor data saving :" + e.getMessage()); + logger.error("Error in doctor data saving: {}", e.getMessage()); response.setError(5000, e.getMessage()); } - return response.toString(); + return response.toStringWithHttpStatus(); } /** @@ -168,29 +158,24 @@ public String saveBenGenOPDDoctorData(@RequestBody String requestObj, @Operation(summary = "Get general OPD beneficiary visit details") @PostMapping(value = { "/getBenVisitDetailsFrmNurseGOPD" }) @Transactional(rollbackFor = Exception.class) - public String getBenVisitDetailsFrmNurseGOPD( + public ResponseEntity getBenVisitDetailsFrmNurseGOPD( @Param(value = "{\"benRegID\":\"Long\",\"visitCode\":\"Long\"}") @RequestBody String comingRequest) { OutputResponse response = new OutputResponse(); - - logger.info("Request obj to fetch General OPD visit details :" + comingRequest); try { JSONObject obj = new JSONObject(comingRequest); if (obj.length() > 1) { Long benRegID = obj.getLong("benRegID"); Long visitCode = obj.getLong("visitCode"); - String res = generalOPDServiceImpl.getBenVisitDetailsFrmNurseGOPD(benRegID, visitCode); response.setResponse(res); } else { - logger.info("Invalid Request Data."); - response.setError(5000, "Invalid request"); + response.setError(OutputResponse.BAD_REQUEST, "Invalid request"); } - logger.info("getBenDataFrmNurseScrnToDocScrnVisitDetails response:" + response); } catch (Exception e) { response.setError(5000, "Error while getting beneficiary visit data"); - logger.error("Error in getBenDataFrmNurseScrnToDocScrnVisitDetails:" + e); + logger.error("Error in getBenVisitDetailsFrmNurseGOPD: {}", e.getMessage()); } - return response.toString(); + return response.toStringWithHttpStatus(); } /** @@ -200,29 +185,24 @@ public String getBenVisitDetailsFrmNurseGOPD( */ @Operation(summary = "Get general OPD beneficiary history") @PostMapping(value = { "/getBenHistoryDetails" }) - - public String getBenHistoryDetails( + public ResponseEntity getBenHistoryDetails( @Param(value = "{\"benRegID\":\"Long\",\"visitCode\":\"Long\"}") @RequestBody String comingRequest) { OutputResponse response = new OutputResponse(); - - logger.info("getBenHistoryDetails request:" + comingRequest); try { JSONObject obj = new JSONObject(comingRequest); if (obj.has("benRegID") && obj.has("visitCode")) { Long benRegID = obj.getLong("benRegID"); Long visitCode = obj.getLong("visitCode"); - String s = generalOPDServiceImpl.getBenHistoryDetails(benRegID, visitCode); response.setResponse(s); } else { - response.setError(5000, "Invalid request"); + response.setError(OutputResponse.BAD_REQUEST, "Invalid request"); } - logger.info("getBenHistoryDetails response:" + response); } catch (Exception e) { response.setError(5000, "Error while getting beneficiary history data"); - logger.error("Error in getBenHistoryDetails:" + e); + logger.error("Error in getBenHistoryDetails: {}", e.getMessage()); } - return response.toString(); + return response.toStringWithHttpStatus(); } /** @@ -232,29 +212,24 @@ public String getBenHistoryDetails( */ @Operation(summary = "Get general OPD beneficiary vitals") @PostMapping(value = { "/getBenVitalDetailsFrmNurse" }) - public String getBenVitalDetailsFrmNurse( + public ResponseEntity getBenVitalDetailsFrmNurse( @Param(value = "{\"benRegID\":\"Long\",\"visitCode\":\"Long\"}") @RequestBody String comingRequest) { OutputResponse response = new OutputResponse(); - - logger.info("getBenVitalDetailsFrmNurse request:" + comingRequest); try { JSONObject obj = new JSONObject(comingRequest); if (obj.has("benRegID") && obj.has("visitCode")) { Long benRegID = obj.getLong("benRegID"); Long visitCode = obj.getLong("visitCode"); - String res = generalOPDServiceImpl.getBeneficiaryVitalDetails(benRegID, visitCode); response.setResponse(res); } else { - logger.info("Invalid Request Data."); - response.setError(5000, "Invalid request"); + response.setError(OutputResponse.BAD_REQUEST, "Invalid request"); } - logger.info("getBenVitalDetailsFrmNurse response:" + response); } catch (Exception e) { response.setError(5000, "Error while getting beneficiary vital data"); - logger.error("Error in getBenVitalDetailsFrmNurse:" + e); + logger.error("Error in getBenVitalDetailsFrmNurse: {}", e.getMessage()); } - return response.toString(); + return response.toStringWithHttpStatus(); } /** @@ -264,29 +239,24 @@ public String getBenVitalDetailsFrmNurse( */ @Operation(summary = "Get general OPD beneficiary examination details") @PostMapping(value = { "/getBenExaminationDetails" }) - - public String getBenExaminationDetails( + public ResponseEntity getBenExaminationDetails( @Param(value = "{\"benRegID\":\"Long\",\"visitCode\":\"Long\"}") @RequestBody String comingRequest) { OutputResponse response = new OutputResponse(); - - logger.info("getBenExaminationDetails request:" + comingRequest); try { JSONObject obj = new JSONObject(comingRequest); if (obj.has("benRegID") && obj.has("visitCode")) { Long benRegID = obj.getLong("benRegID"); Long visitCode = obj.getLong("visitCode"); - String s = generalOPDServiceImpl.getExaminationDetailsData(benRegID, visitCode); response.setResponse(s); } else { - response.setError(5000, "Invalid request"); + response.setError(OutputResponse.BAD_REQUEST, "Invalid request"); } - logger.info("getBenExaminationDetails response:" + response); } catch (Exception e) { response.setError(5000, "Error while getting beneficiary examination data"); - logger.error("Error in getBenExaminationDetails:" + e); + logger.error("Error in getBenExaminationDetails: {}", e.getMessage()); } - return response.toString(); + return response.toStringWithHttpStatus(); } /** @@ -297,29 +267,24 @@ public String getBenExaminationDetails( @Operation(summary = "Get general OPD beneficiary case record and referral") @PostMapping(value = { "/getBenCaseRecordFromDoctorGeneralOPD" }) @Transactional(rollbackFor = Exception.class) - public String getBenCaseRecordFromDoctorGeneralOPD( + public ResponseEntity getBenCaseRecordFromDoctorGeneralOPD( @Param(value = "{\"benRegID\":\"Long\",\"visitCode\":\"Long\"}") @RequestBody String comingRequest) { OutputResponse response = new OutputResponse(); - - logger.info("getBenCaseRecordFromDoctorGeneralOPD request:" + comingRequest); try { JSONObject obj = new JSONObject(comingRequest); if (null != obj && obj.length() > 1 && obj.has("benRegID") && obj.has("visitCode")) { Long benRegID = obj.getLong("benRegID"); Long visitCode = obj.getLong("visitCode"); - String res = generalOPDServiceImpl.getBenCaseRecordFromDoctorGeneralOPD(benRegID, visitCode); response.setResponse(res); } else { - logger.info("Invalid Request Data."); - response.setError(5000, "Invalid request"); + response.setError(OutputResponse.BAD_REQUEST, "Invalid request"); } - logger.info("getBenCaseRecordFromDoctorGeneralOPD response:" + response); } catch (Exception e) { response.setError(5000, "Error while getting beneficiary doctor data"); - logger.error("Error in getBenCaseRecordFromDoctorGeneralOPD:" + e); + logger.error("Error in getBenCaseRecordFromDoctorGeneralOPD: {}", e.getMessage()); } - return response.toString(); + return response.toStringWithHttpStatus(); } /** @@ -331,30 +296,21 @@ public String getBenCaseRecordFromDoctorGeneralOPD( */ @Operation(summary = "Update beneficiary history") @PostMapping(value = { "/update/historyScreen" }) - public String updateHistoryNurse(@RequestBody String requestObj) { - + public ResponseEntity updateHistoryNurse(@RequestBody String requestObj) { OutputResponse response = new OutputResponse(); - logger.info("Request object for history data updating :" + requestObj); - - JsonObject jsnOBJ = new JsonObject(); - JsonParser jsnParser = new JsonParser(); - JsonElement jsnElmnt = jsnParser.parse(requestObj); - jsnOBJ = jsnElmnt.getAsJsonObject(); - + JsonObject jsnOBJ = new JsonParser().parse(requestObj).getAsJsonObject(); try { int result = generalOPDServiceImpl.updateBenHistoryDetails(jsnOBJ); if (result > 0) { response.setResponse("Data updated successfully"); } else { - response.setError(500, "Unable to modify data"); + response.setError(OutputResponse.GENERIC_FAILURE, "Unable to modify data"); } - logger.info("History data update response:" + response); } catch (Exception e) { response.setError(5000, "Unable to modify data"); - logger.error("Error while updating history data :" + e); + logger.error("Error while updating history data: {}", e.getMessage()); } - - return response.toString(); + return response.toStringWithHttpStatus(); } /** @@ -366,30 +322,21 @@ public String updateHistoryNurse(@RequestBody String requestObj) { */ @Operation(summary = "Update general OPD beneficiary vitals") @PostMapping(value = { "/update/vitalScreen" }) - public String updateVitalNurse(@RequestBody String requestObj) { - + public ResponseEntity updateVitalNurse(@RequestBody String requestObj) { OutputResponse response = new OutputResponse(); - logger.info("Request object for vital data updating :" + requestObj); - - JsonObject jsnOBJ = new JsonObject(); - JsonParser jsnParser = new JsonParser(); - JsonElement jsnElmnt = jsnParser.parse(requestObj); - jsnOBJ = jsnElmnt.getAsJsonObject(); - + JsonObject jsnOBJ = new JsonParser().parse(requestObj).getAsJsonObject(); try { int result = generalOPDServiceImpl.updateBenVitalDetails(jsnOBJ); if (result > 0) { response.setResponse("Data updated successfully"); } else { - response.setError(500, "Unable to modify data"); + response.setError(OutputResponse.GENERIC_FAILURE, "Unable to modify data"); } - logger.info("Vital data update response:" + response); } catch (Exception e) { response.setError(5000, "Unable to modify data"); - logger.error("Error while updating vital data :" + e); + logger.error("Error while updating vital data: {}", e.getMessage()); } - - return response.toString(); + return response.toStringWithHttpStatus(); } /** @@ -401,30 +348,21 @@ public String updateVitalNurse(@RequestBody String requestObj) { */ @Operation(summary = "Update general OPD beneficiary examination data") @PostMapping(value = { "/update/examinationScreen" }) - public String updateGeneralOPDExaminationNurse(@RequestBody String requestObj) { - + public ResponseEntity updateGeneralOPDExaminationNurse(@RequestBody String requestObj) { OutputResponse response = new OutputResponse(); - logger.info("Request object for examination data updating :" + requestObj); - - JsonObject jsnOBJ = new JsonObject(); - JsonParser jsnParser = new JsonParser(); - JsonElement jsnElmnt = jsnParser.parse(requestObj); - jsnOBJ = jsnElmnt.getAsJsonObject(); - + JsonObject jsnOBJ = new JsonParser().parse(requestObj).getAsJsonObject(); try { int result = generalOPDServiceImpl.updateBenExaminationDetails(jsnOBJ); if (result > 0) { response.setResponse("Data updated successfully"); } else { - response.setError(500, "Unable to modify data"); + response.setError(OutputResponse.GENERIC_FAILURE, "Unable to modify data"); } - logger.info("Examination data update response:" + response); } catch (Exception e) { response.setError(5000, "Unable to modify data"); - logger.error("Error while updating examination data :" + e); + logger.error("Error while updating examination data: {}", e.getMessage()); } - - return response.toString(); + return response.toStringWithHttpStatus(); } /** @@ -435,21 +373,13 @@ public String updateGeneralOPDExaminationNurse(@RequestBody String requestObj) { */ @Operation(summary = "Update general OPD beneficiary case record and referral") @PostMapping(value = { "/update/doctorData" }) - public String updateGeneralOPDDoctorData(@RequestBody String requestObj, + public ResponseEntity updateGeneralOPDDoctorData(@RequestBody String requestObj, @RequestHeader(value = "Authorization") String Authorization) { - OutputResponse response = new OutputResponse(); - logger.info("Request object for doctor data updating :" + requestObj); - - JsonObject jsnOBJ = new JsonObject(); - JsonParser jsnParser = new JsonParser(); - JsonElement jsnElmnt = jsnParser.parse(requestObj); - jsnOBJ = jsnElmnt.getAsJsonObject(); - + JsonObject jsnOBJ = new JsonParser().parse(requestObj).getAsJsonObject(); try { Long result = generalOPDServiceImpl.updateGeneralOPDDoctorData(jsnOBJ, Authorization); if (null != result && result > 0) { - // Extract drug IDs from JsonObject List prescribedDrugIDs = new ArrayList<>(); if (jsnOBJ.has("savedDrugIDs") && !jsnOBJ.get("savedDrugIDs").isJsonNull()) { JsonArray drugIDsArray = jsnOBJ.getAsJsonArray("savedDrugIDs"); @@ -457,25 +387,18 @@ public String updateGeneralOPDDoctorData(@RequestBody String requestObj, prescribedDrugIDs.add(drugIDsArray.get(j).getAsLong()); } } - - // Create response with message and IDs Map responseData = new HashMap<>(); responseData.put("message", "Data updated successfully"); responseData.put("prescribedDrugIDs", prescribedDrugIDs); - - Gson gson = new Gson(); - String responseJson = gson.toJson(responseData); - response.setResponse(responseJson); + response.setResponse(new Gson().toJson(responseData)); } else { - response.setError(500, "Unable to modify data"); + response.setError(OutputResponse.GENERIC_FAILURE, "Unable to modify data"); } - logger.info("Doctor data update response:" + response); } catch (Exception e) { - logger.error("Unable to modify data. " + e.getMessage()); + logger.error("Unable to modify doctor data: {}", e.getMessage()); response.setError(5000, e.getMessage()); } - - return response.toString(); + return response.toStringWithHttpStatus(); } } diff --git a/src/main/java/com/iemr/hwc/utils/response/OutputResponse.java b/src/main/java/com/iemr/hwc/utils/response/OutputResponse.java index 8b78f516..972f6afa 100644 --- a/src/main/java/com/iemr/hwc/utils/response/OutputResponse.java +++ b/src/main/java/com/iemr/hwc/utils/response/OutputResponse.java @@ -51,8 +51,8 @@ public class OutputResponse { public static final int ENVIRONMENT_EXCEPTION = 5006; public static final int PARSE_EXCEPTION = 5007; public static final int SWYMED_EXCEPTION = 5010; - public static final int TM_EXCEPTION = 5010; - public static final int BAD_REQUEST = 404; + public static final int TM_EXCEPTION = 5011; + public static final int BAD_REQUEST = 400; @Expose private int statusCode = GENERIC_FAILURE; @@ -235,12 +235,22 @@ public ResponseEntity toStringWithHttpStatus() { switch (this.statusCode) { case SUCCESS: return ResponseEntity.status(HttpStatus.OK).body(output); - case GENERIC_FAILURE: - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(output); case BAD_REQUEST: + case OBJECT_FAILURE: + case PARSE_EXCEPTION: return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(output); + case USERID_FAILURE: + case PASSWORD_FAILURE: + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(output); + case PREVILAGE_FAILURE: + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(output); + case GENERIC_FAILURE: + case CODE_EXCEPTION: + case SWYMED_EXCEPTION: + case TM_EXCEPTION: + case ENVIRONMENT_EXCEPTION: default: - return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(output); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(output); } // if(!isSuccess())