From 7da06bd659c381413199c434570555e3d0422a33 Mon Sep 17 00:00:00 2001 From: Maksim Tereshin Date: Mon, 16 Dec 2024 10:22:36 +0100 Subject: [PATCH] Improved logging and exception handling --- .../org/micord/controller/ApiController.java | 126 +++++++----------- .../exceptions/GlobalExceptionHandler.java | 106 +++++++++++++++ .../org/micord/service/RequestService.java | 124 +++++++++-------- 3 files changed, 220 insertions(+), 136 deletions(-) create mode 100644 config-data-executor/src/main/java/org/micord/exceptions/GlobalExceptionHandler.java diff --git a/config-data-executor/src/main/java/org/micord/controller/ApiController.java b/config-data-executor/src/main/java/org/micord/controller/ApiController.java index 294f4f8..53ff3f8 100644 --- a/config-data-executor/src/main/java/org/micord/controller/ApiController.java +++ b/config-data-executor/src/main/java/org/micord/controller/ApiController.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.sql.SQLException; import java.util.List; import java.util.Map; @@ -39,98 +40,58 @@ public class ApiController { private ValidationService validationService; @PostMapping("/block") - public ResponseEntity block(@RequestBody List ids) { - try { - validationService.validateAll(ids); + public ResponseEntity block(@RequestBody List ids) throws SQLException, FileNotFoundException { + validationService.validateAll(ids); - logger.debug("Starting block process for ids: {}", ids); - apiService.process(ConfigType.BLOCK, ids); - logger.debug("Finished block process for ids: {}", ids); - return ResponseEntity.ok("Операция \"Блокировка\" завершена успешно."); - } catch (ValidationException e) { - logger.error("Validation failed for IDs: {}", ids, e); - return ResponseEntity.badRequest().body(Map.of( - "message", "Validation error occurred", - "details", e.getValidationDetails() - )); - } catch (Exception e) { - logger.error("Unexpected error during processing: {}", e.getMessage(), e); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred."); - } + logger.debug("Starting block process for ids: {}", ids); + apiService.process(ConfigType.BLOCK, ids); + logger.debug("Finished block process for ids: {}", ids); + + return ResponseEntity.ok("Операция \"Блокировка\" завершена успешно."); } + @PostMapping("/unblock") - public ResponseEntity unblock(@RequestBody List ids) { - try { - validationService.validateAll(ids); + public ResponseEntity unblock(@RequestBody List ids) throws SQLException, FileNotFoundException { + validationService.validateAll(ids); - logger.debug("Starting unblock process for ids: {}", ids); - apiService.process(ConfigType.UNBLOCK, ids); - logger.debug("Finished unblock process for ids: {}", ids); - return ResponseEntity.ok("Операция \"Разблокировка\" завершена успешно."); - } catch (ValidationException e) { - logger.error("Validation failed for IDs: {}", ids, e); - return ResponseEntity.badRequest().body(Map.of( - "message", "Validation error occurred", - "details", e.getValidationDetails() - )); - } catch (Exception e) { - logger.error("Unexpected error during processing: {}", e.getMessage(), e); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred."); - } + logger.debug("Starting unblock process for ids: {}", ids); + apiService.process(ConfigType.UNBLOCK, ids); + logger.debug("Finished unblock process for ids: {}", ids); + + return ResponseEntity.ok("Операция \"Разблокировка\" завершена успешно."); } + @PostMapping("/removeFromSystem") - public ResponseEntity removeFromSystem(@RequestBody List ids) { - try { - validationService.validateAll(ids); + public ResponseEntity removeFromSystem(@RequestBody List ids) throws SQLException, FileNotFoundException { + validationService.validateAll(ids); - logger.debug("Starting removeFromSystem process for ids: {}", ids); - apiService.process(ConfigType.REMOVE_FROM_SYSTEM, ids); - logger.debug("Finished removeFromSystem process for ids: {}", ids); - return ResponseEntity.ok("Операция \"Удаление данных по гражданину\" завершена успешно."); - } catch (ValidationException e) { - logger.error("Validation failed for IDs: {}", ids, e); - return ResponseEntity.badRequest().body(Map.of( - "message", "Validation error occurred", - "details", e.getValidationDetails() - )); - } catch (Exception e) { - logger.error("Unexpected error during processing: {}", e.getMessage(), e); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred."); - } + logger.debug("Starting removeFromSystem process for ids: {}", ids); + apiService.process(ConfigType.REMOVE_FROM_SYSTEM, ids); + logger.debug("Finished removeFromSystem process for ids: {}", ids); + + return ResponseEntity.ok("Операция \"Удаление данных по гражданину\" завершена успешно."); } + @PostMapping("/removeFromCallList") - public ResponseEntity removeFromCallList(@RequestBody List ids) { - try { - validationService.validateAll(ids); + public ResponseEntity removeFromCallList(@RequestBody List ids) throws SQLException, FileNotFoundException { + validationService.validateAll(ids); - logger.debug("Starting removeFromCallList process for ids: {}", ids); - apiService.process(ConfigType.REMOVE_FROM_CALL_LIST, ids); - logger.debug("Finished removeFromCallList process for ids: {}", ids); - return ResponseEntity.ok("Операция \"Удаление из списков на вызов\" завершена успешно."); - } catch (ValidationException e) { - logger.error("Validation failed for IDs: {}", ids, e); - return ResponseEntity.badRequest().body(Map.of( - "message", "Validation error occurred", - "details", e.getValidationDetails() - )); - } catch (Exception e) { - logger.error("Unexpected error during processing: {}", e.getMessage(), e); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred."); - } + logger.debug("Starting removeFromCallList process for ids: {}", ids); + apiService.process(ConfigType.REMOVE_FROM_CALL_LIST, ids); + logger.debug("Finished removeFromCallList process for ids: {}", ids); + + return ResponseEntity.ok("Операция \"Удаление из списков на вызов\" завершена успешно."); } + @PostMapping("/downloadCSV") - public ResponseEntity downloadCSV(@RequestBody DownloadCSVRequest request) - throws IOException { + public ResponseEntity downloadCSV(@RequestBody DownloadCSVRequest request) throws IOException { logger.debug("Starting downloadCSV process for request: {}", request.getType()); - if (request.getStartDate() != null && request.getEndDate() != null) { - if (request.getStartDate().isAfter(request.getEndDate())) { - throw new IllegalArgumentException("Start date must be before end date"); - } - } + + validateRequestDates(request); File csvFile = apiService.download(ConfigType.DOWNLOAD_CSV, request); InputStreamResource resource = new InputStreamResource(new FileInputStream(csvFile)); @@ -144,11 +105,22 @@ public class ApiController { .body(resource); } + private void validateRequestDates(DownloadCSVRequest request) { + if (request.getStartDate() != null && request.getEndDate() != null) { + if (request.getStartDate().isAfter(request.getEndDate())) { + throw new IllegalArgumentException("Start date must be before end date"); + } + } + } + + @GetMapping("/listDownloadTypes") - public ResponseEntity listDownloadTypes() - throws FileNotFoundException { + public ResponseEntity> listDownloadTypes() throws FileNotFoundException { + logger.debug("Fetching list of download types..."); + List downloadCSVTypes = apiService.getDownloadTypes(ConfigType.DOWNLOAD_CSV); + logger.debug("Successfully retrieved download types"); return ResponseEntity.ok(downloadCSVTypes); } } diff --git a/config-data-executor/src/main/java/org/micord/exceptions/GlobalExceptionHandler.java b/config-data-executor/src/main/java/org/micord/exceptions/GlobalExceptionHandler.java new file mode 100644 index 0000000..9f560d1 --- /dev/null +++ b/config-data-executor/src/main/java/org/micord/exceptions/GlobalExceptionHandler.java @@ -0,0 +1,106 @@ +package org.micord.exceptions; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.naming.ServiceUnavailableException; +import java.io.FileNotFoundException; +import java.nio.file.AccessDeniedException; +import java.sql.SQLException; +import java.util.Map; +import java.util.NoSuchElementException; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + @ExceptionHandler(SQLException.class) + public ResponseEntity handleSQLException(SQLException e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of( + "message", "Database error occurred", + "details", e.getMessage() + )); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGeneralException(Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of( + "message", "Unexpected error occurred", + "details", e.getMessage() + )); + } + + @ExceptionHandler(ValidationException.class) + public ResponseEntity handleValidationException(ValidationException e) { + return ResponseEntity.badRequest().body(Map.of( + "message", "Validation error occurred", + "details", e.getValidationDetails() + )); + } + + @ExceptionHandler(IllegalStateException.class) + public ResponseEntity handleIllegalStateException(IllegalStateException e) { + return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of( + "message", "Operation cannot be performed due to an invalid state", + "details", e.getMessage() + )); + } + + @ExceptionHandler(AccessDeniedException.class) + public ResponseEntity handleAccessDeniedException(AccessDeniedException e) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(Map.of( + "message", "Access denied", + "details", e.getMessage() + )); + } + + @ExceptionHandler(ServiceUnavailableException.class) + public ResponseEntity handleServiceUnavailableException(ServiceUnavailableException e) { + return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(Map.of( + "message", "Service is temporarily unavailable", + "details", e.getMessage() + )); + } + + @ExceptionHandler(FileNotFoundException.class) + public ResponseEntity handleFileNotFoundException(FileNotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of( + "message", "File not found", + "details", e.getMessage() + )); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { + return ResponseEntity.badRequest().body(Map.of( + "message", "Invalid input provided", + "details", e.getMessage() + )); + } + + @ExceptionHandler(NoSuchElementException.class) + public ResponseEntity handleNoSuchElementException(NoSuchElementException e) { + logger.error("Resource not found: {}", e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of( + "message", "Requested resource not found", + "details", e.getMessage() + )); + } + + @ExceptionHandler(RuntimeException.class) + public ResponseEntity handleRuntimeException(RuntimeException e) { + logger.error("Unexpected error occurred: {}", e.getMessage(), e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of( + "message", "Internal server error", + "details", e.getMessage() + )); + } + + + +} diff --git a/config-data-executor/src/main/java/org/micord/service/RequestService.java b/config-data-executor/src/main/java/org/micord/service/RequestService.java index be88d57..4199d8f 100644 --- a/config-data-executor/src/main/java/org/micord/service/RequestService.java +++ b/config-data-executor/src/main/java/org/micord/service/RequestService.java @@ -16,7 +16,13 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.InputSource; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.StringReader; import java.net.HttpURLConnection; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -26,7 +32,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; -import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; /** @@ -42,24 +47,9 @@ public class RequestService { public void processS3Requests(List s3Requests, List ids) { logger.debug("Starting processing of S3 requests"); - if (s3Requests != null && !s3Requests.isEmpty()) { - List> requestFutures = s3Requests.stream() - .map(request -> CompletableFuture.runAsync(() -> { - try { - processS3Request(request, ids); - } catch (Exception e) { - throw new RuntimeException("Error processing S3 request: " + request, e); - } - })) - .toList(); - CompletableFuture.allOf(requestFutures.toArray(new CompletableFuture[0])) - .whenComplete((result, ex) -> { - if (ex != null) { - logger.error("Error processing S3 requests", ex); - throw new RuntimeException("Error processing S3 requests", ex); - } - }); + for (S3Request request : s3Requests) { + processS3Request(request, ids); } } @@ -82,61 +72,75 @@ public class RequestService { } long endExecTime = System.currentTimeMillis(); logger.debug("Paths fetched in {} ms", endExecTime - startExecTime); - } - catch (SQLException e) { - logger.error("Failed to execute query for RequestArgument", e); - throw new RuntimeException("Error executing database query: " + argument.getRequestArgumentURL(), e); + } catch (SQLException e) { + logger.error("Failed to execute query for RequestArgument: {}", argument.getRequestArgumentURL(), e); + throw new RuntimeException("Database query error for argument: " + argument.getRequestArgumentURL(), e); } } } if (files.isEmpty()) { logger.warn("No files found for S3 request"); - throw new RuntimeException("No files found for S3 request"); + throw new NoSuchElementException("No files found for S3 request"); } - files.forEach(file -> { - HttpRequest httpRequest; - logger.debug("Starting building HTTP request for S3 request"); - long startExecTime = System.currentTimeMillis(); - - if (file == null || file.isBlank()) { - logger.warn("Skipping invalid file path: {}", file); - throw new RuntimeException("Invalid file path"); - } + for (String file : files) { try { - httpRequest = S3HttpConnection.buildHttpRequest(request, file); + processFileForS3Request(request, file); + } catch (RuntimeException e) { + logger.error("Error processing file: {}", file, e); + throw e; // Rethrow to propagate for exception handling } - catch (Exception e) { - throw new RuntimeException(e); - } - long endExecTime = System.currentTimeMillis(); - logger.debug("HTTP request built in {} ms", endExecTime - startExecTime); + } - httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString()) - .thenAccept(response -> { - if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT - || response.statusCode() == HttpURLConnection.HTTP_OK) { - logger.info("Successfully deleted object {}", file); - } - else { - logger.error("Failed to delete object {}. Response code: {}", file, - response.statusCode() - ); - } - }) - .exceptionally(ex -> { - logger.error("Failed to delete object {}", file, ex); - return null; - }); - }); - - } - catch (Exception e) { + } catch (Exception e) { logger.error("Failed to process S3 request: {}", request, e); + throw e; // Rethrow exception to propagate to the handler } } + private void processFileForS3Request(S3Request request, String file) { + if (file == null || file.isBlank()) { + logger.warn("Skipping invalid file path: {}", file); + throw new RuntimeException("Invalid file path"); + } + + try { + logger.debug("Starting building HTTP request for file: {}", file); + long startExecTime = System.currentTimeMillis(); + HttpRequest httpRequest = S3HttpConnection.buildHttpRequest(request, file); + long endExecTime = System.currentTimeMillis(); + logger.debug("HTTP request built in {} ms for file: {}", endExecTime - startExecTime, file); + + HttpResponse response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT || response.statusCode() == HttpURLConnection.HTTP_OK) { + logger.info("Successfully deleted object {}", file); + } else { + handleErrorResponse(response, file); + } + } catch (Exception e) { + logger.error("Error sending HTTP request for file: {}", file, e); + throw new RuntimeException("HTTP request error for file: " + file, e); + } + } + + private void handleErrorResponse(HttpResponse response, String file) { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + InputSource is = new InputSource(new StringReader(response.body())); + Document doc = builder.parse(is); + Element root = doc.getDocumentElement(); + String message = root.getElementsByTagName("Message").item(0).getTextContent(); + logger.error("Failed to delete object {}. Response code: {}. Error message: {}", file, response.statusCode(), message); + throw new RuntimeException("Failed to delete object: " + file); + } catch (Exception e) { + logger.error("Failed to parse error response for file {}", file, e); + throw new RuntimeException("Error parsing HTTP response: " + response.body(), e); + } + } + + @Transactional public void processSqlAndAqlRequests(Requests config, List ids) { logger.debug("Starting transactional processing of requests"); @@ -151,7 +155,9 @@ public class RequestService { processAqlRequests(request, ids); } } - processS3Requests(config.getS3Requests(), ids); + if (config.getS3Requests() != null && !config.getS3Requests().isEmpty()) { + processS3Requests(config.getS3Requests(), ids); + } } private void processSqlRequests(SqlRequest request, List ids) {