From 05f64af4047a0306401bcf2419fb115a5bbcb405 Mon Sep 17 00:00:00 2001 From: piyushkag Date: Mon, 9 Dec 2024 19:27:54 +0530 Subject: [PATCH] Updated code for Ndg generation with async processing. --- .../tendermanagement/dao/AppointmentDao.java | 328 +++++++++--------- .../tendermanagement/util/LoggingUtil.java | 1 - .../gepafin/tendermanagement/util/Utils.java | 38 ++ src/main/resources/message_en.properties | 2 +- src/main/resources/message_it.properties | 2 +- 5 files changed, 197 insertions(+), 174 deletions(-) diff --git a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java index ef594da5..86a6aceb 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java @@ -129,82 +129,155 @@ public class AppointmentDao { public NdgResponse checkNdgForAppointment(Long applicationId) { + ApplicationEntity application = applicationService.validateApplication(applicationId); + NdgResponse ndgResponse = new NdgResponse(); + if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_IN_PROGRESS)) { + throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS)); + } + + if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_GENERATED) && application.getNdg() != null) { + ndgResponse.setNdg(application.getNdg()); + return ndgResponse; + } + + // Update application status + application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS); + applicationRepository.save(application); + + // Start async processing + startAsyncNdgProcessing(applicationId); + + throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS)); + } + + private void startAsyncNdgProcessing(Long applicationId) { + // Check if a thread is already running for this application + if (executorMap.containsKey(applicationId)) { + log.warn("Async processing already running for applicationId: {}", applicationId); + return; + } + + // Create a dedicated thread for asynchronous processing + ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { + Thread thread = new Thread(runnable); + thread.setName("AsyncNdgProcessing-" + applicationId); + return thread; + }); + executorMap.put(applicationId, executor); + + executor.submit(() -> { + try { + log.info("Starting async processing for applicationId: {}", applicationId); + processNdgGeneration(applicationId); + } catch (Exception e) { + log.error("Error in async NDG processing for applicationId: {}", applicationId, e); + } finally { + // Cleanup resources + ExecutorService executorToShutdown = executorMap.remove(applicationId); + if (executorToShutdown != null) { + executorToShutdown.shutdown(); + } + log.info("Async processing completed for applicationId: {}", applicationId); + } + }); + } + + private void processNdgGeneration(Long applicationId) { + // Validate application, company, and hub + ApplicationEntity application = applicationService.validateApplication(applicationId); + CompanyEntity company = companyService.validateCompany(application.getCompanyId()); + HubEntity hub = hubRepository.findByHubId(application.getHubId()); + + if (!hub.getUniqueUuid().equals(defaultHubUuid)) { + log.info("Ndg cannot be created for another Hub, it is default for Gepafin."); + throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_NDG_FOR_ANOTHER_HUB)); + } + try { - NdgResponse ndgResponseToReturn = new NdgResponse(); - // Validate application, company, and hub - ApplicationEntity application = applicationService.validateApplication(applicationId); - if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_IN_PROGRESS)) { - throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS)); - } - //cloned for old application data - ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); - - CompanyEntity company = companyService.validateCompany(application.getCompanyId()); - HubEntity hub = hubRepository.findByHubId(application.getHubId()); - - if (!hub.getUniqueUuid().equals(defaultHubUuid)) { - log.info("Ndg cannot be created for another Hub, it is default for Gepafin."); - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_NDG_FOR_ANOTHER_HUB)); - } - - // Check if NDG and idVisura are already present - if (isNdgAndIdVisuraPresent(application)) { - log.info("NDG already exist for applicationId: {}", applicationId); - ndgResponseToReturn.setNdg(application.getNdg()); - return ndgResponseToReturn; - } - // Authenticate and fetch token if required - if (hub.getAppointmentAuthTokenId() == null && hub.getAreaCode() == null) { - hub = authenticateAndSaveToken(hub); + if (hub.getAppointmentAuthTokenId() == null || hub.getAreaCode() == null) { + authenticateAndSaveToken(hub); } String authorizationToken = getBearerToken(hub); // Try retrieving NDG by VAT number AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); - //For testing purpose Commenting it if (isNdgValid(ndgResponse.getNdg())) { saveNdgAndIdVisura(application, company, ndgResponse.getNdg(), null); - ndgResponseToReturn.setNdg(application.getNdg()); - return ndgResponseToReturn; + log.info("NDG successfully generated for applicationId: {}", applicationId); + } else { + // If NDG isn't immediately available, start polling + handleNdgPolling(application, company, hub, authorizationToken); } - - return getNdgResponse(company, authorizationToken, hub, application, ndgResponseToReturn, oldApplicationData); - } catch (FeignException e) { - log.error("Error in feign client call during NDG handling: {}", e.getMessage(), e); - Utils.callException(e.status(), e); - } catch (CustomValidationException e) { - log.info("Custom validation exception: {}", e.getMessage()); - throw e; } catch (Exception e) { - log.error("Error during NDG handling: {}", e.getMessage(), e); - throw new RuntimeException("Error during fetching NDG."); + log.error("Error during NDG generation for applicationId: {}", applicationId, e); } - throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); } - private NdgResponse getNdgResponse(CompanyEntity company, String authorizationToken, HubEntity hub, ApplicationEntity application, NdgResponse ndgResponseToReturn, - ApplicationEntity oldApplicationData) { - // Create Visura if NDG is not found - AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); - if (isNdgValid(visuraResponse.getNdg())) { - saveNdgAndIdVisura(application, company, visuraResponse.getNdg(), visuraResponse.getIdVisura()); - ndgResponseToReturn.setNdg(application.getNdg()); - } else if (visuraResponse.getIdVisura() != null) { - application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - applicationRepository.save(application); + private void handleNdgPolling(ApplicationEntity application, CompanyEntity company, HubEntity hub, String authorizationToken) { - /** This code is responsible for adding a version history log for the "Updating ndg status in application" operation. **/ - loggingUtil.addVersionHistory( - VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build()); + try { + log.info("Starting NDG polling for applicationId: {}", application.getId()); + long startTime = System.currentTimeMillis(); - startNdgPollingTask(application, company, hub); - throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.NDG_GENERATION_IS_IN_PROGRESS)); + while (true) { + if (application.getNdg() != null) { + log.info("NDG retrieved for applicationId: {}", application.getId()); + break; + } + + try { + // Fetch Visura list and attempt to parse NDG + String visuraListJson = getVisuraList(application.getIdVisura(), authorizationToken, application, hub); + String ndg = parseNdgFromVisuraListResponse(visuraListJson); + + if (isNdgValid(ndg)) { + // CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); + // ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); + + company.setNdg(ndg); + application.setNdg(ndg); + application.setNdgStatus(GepafinConstant.NDG_GENERATED); + application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); + applicationRepository.save(application); + companyRepository.save(company); + log.info("NDG saved successfully for applicationId: {}", application.getId()); + + // /** This code is responsible for adding a version history log for the "update application ndg code, status, and Id visura" + // operation. **/ + // loggingUtil.addVersionHistory( + // VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData) + // .newData(application).build()); + // + // /** This code is responsible for adding a version history log for the "update company ndg code" operation. **/ + // loggingUtil.addVersionHistory( + // VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyData) + // .newData(company).build()); + break; + } + + // Check if polling has timed out + if (System.currentTimeMillis() - startTime > TimeUnit.HOURS.toMillis(2)) { + log.warn("NDG polling timed out for applicationId: {}", application.getId()); + application.setNdgStatus(GepafinConstant.NDG_FAILED); + applicationRepository.save(application); + break; + } + + // Wait before the next polling attempt + Thread.sleep(TimeUnit.MINUTES.toMillis(15)); + } catch (InterruptedException e) { + log.warn("NDG polling interrupted for applicationId: {}", application.getId()); + Thread.currentThread().interrupt(); + break; + } catch (Exception e) { + log.error("Error during NDG polling for applicationId: {}", application.getId(), e); + } + } + } finally { + log.info("NDG polling completed for applicationId: {}", application.getId()); } - - return ndgResponseToReturn; } private static String getBearerToken(HubEntity hub) { @@ -212,102 +285,6 @@ public class AppointmentDao { return "Bearer " + hub.getAppointmentAuthTokenId(); } - private void startNdgPollingTask(ApplicationEntity application, CompanyEntity company, HubEntity hub) { - //cloned for all data - ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); - CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); - - // Check if a thread is already running for this application - if (executorMap.containsKey(application.getId())) { - log.warn("Polling task already running for applicationId: {}", application.getId()); - return; - } - - // Create a dedicated thread (single-threaded executor) for this application - ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { - Thread thread = new Thread(runnable); - thread.setName(GepafinConstant.POLLING_THREAD_NAME + application.getId()); - return thread; - }); - executorMap.put(application.getId(), executor); - - // Submit polling task to this thread - executor.submit(() -> { - try { - log.info("Polling task started for applicationId: {} on thread: {}", application.getId(), Thread.currentThread().getName()); - long startTime = System.currentTimeMillis(); - - while (true) { - if (application.getNdg() != null) - break; - try { - String visuraListJson = getVisuraList(application.getIdVisura(), hub.getAppointmentAuthTokenId(), application, hub); - String ndg = parseNdgFromVisuraListResponse(visuraListJson); - - if (isNdgValid(ndg)) { - company.setNdg(ndg); - application.setNdgStatus(GepafinConstant.NDG_GENERATED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - application.setNdg(ndg); - applicationRepository.save(application); - companyRepository.save(company); - - log.info("NDG obtained for applicationId: {} and saved successfully.", application.getId()); - break; // Exit the loop after successful NDG retrieval - } else { - log.warn("NDG not found for applicationId: {} in Visura List API response.", application.getId()); - } - - // Check if polling time has exceeded the limit - if (System.currentTimeMillis() - startTime > TimeUnit.HOURS.toMillis(2)) { - log.warn("Polling timed out for applicationId: {}", application.getId()); - // Mark NDG status as FAILED for this application - application.setNdgStatus(GepafinConstant.NDG_FAILED); - application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); - applicationRepository.save(application); - log.info("NDG status marked as FAILED for applicationId: {}", application.getId()); - break; - } - - // Wait before the next polling attempt - Thread.sleep(TimeUnit.MINUTES.toMillis(15)); - - } catch (InterruptedException e) { - log.warn("Polling task interrupted for applicationId: {}", application.getId()); - Thread.currentThread().interrupt(); - break; - } catch (Exception e) { - log.error("Error during NDG polling for applicationId: {}", application.getId(), e); - } - } - } finally { - // Cleanup: Shut down the thread for this application - executor.shutdown(); - executorMap.remove(application.getId()); - log.info("Polling task completed and thread shut down for applicationId: {}", application.getId()); - } - }); - /** This code is responsible for adding a version history log for the "Update application ndgCode and ndgStatus" operation. **/ - loggingUtil.addVersionHistory( - VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build()); - - /** This code is responsible for adding a version history log for the "Update company ndgCode" operation. **/ - loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyData).newData(company).build()); - } - - private boolean isNdgAndIdVisuraPresent(ApplicationEntity application) { - - String ndg = application.getNdg(); - String idVisura = application.getIdVisura(); - - if (ndg != null && idVisura == null) { - return true; - } else if (ndg == null && idVisura != null) { - return false; - } else - return ndg != null; - } - private boolean isNdgValid(String ndg) { return ndg != null && !ndg.isEmpty(); @@ -316,8 +293,8 @@ public class AppointmentDao { private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg, String idVisura) { //cloned for old application and company data - ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); - CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); + // ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); + // CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); application.setNdg(ndg); application.setIdVisura(idVisura); @@ -327,12 +304,13 @@ public class AppointmentDao { companyRepository.save(company); applicationRepository.save(application); - /** This code is responsible for adding a version history log for the "update application ndg code, status, and Id visura" operation. **/ - loggingUtil.addVersionHistory( - VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build()); - - /** This code is responsible for adding a version history log for the "update company ndg code" operation. **/ - loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyData).newData(company).build()); + // /** This code is responsible for adding a version history log for the "update application ndg code, status, and Id visura" operation. **/ + // loggingUtil.addVersionHistory( + // VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build()); + // + // /** This code is responsible for adding a version history log for the "update company ndg code" operation. **/ + // loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyData).newData + // (company).build()); log.info("NDG saved for applicationId: {}, {}", application.getId(), application.getNdg()); } @@ -361,7 +339,7 @@ public class AppointmentDao { private HubEntity authenticateAndSaveToken(HubEntity hub) { - HubEntity oldHubData = Utils.getClonedEntityForData(hub); + // HubEntity oldHubData = Utils.getClonedEntityForData(hub); try { //code to generate token with payload having "iat" epoch timestamp and secret key with no expiry and send in below method call String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); @@ -369,8 +347,9 @@ public class AppointmentDao { hub.setAuthToken(authJwtToken); hubRepository.save(hub); - /** This code is responsible for adding a version history log for the "Updating auth token for login api in hub" operation. **/ - loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldHubData).newData(hub).build()); + // /** This code is responsible for adding a version history log for the "Updating auth token for login api in hub" operation. **/ + // loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldHubData).newData + // (hub).build()); // Prepare the request body (adjust if necessary for login API) Map body = Collections.emptyMap(); @@ -389,10 +368,12 @@ public class AppointmentDao { hub.setAreaCode(parsedResponse.getAreaCode()); hubRepository.save(hub); - /** This code is responsible for adding a version history log for the "inserting token and areaCode from login odessa response for appointment flow api's" - * operation. **/ - loggingUtil.addVersionHistory( - VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldHubData).newData(hub).build()); + // /** This code is responsible for adding a version history log for the "inserting token and areaCode from login odessa response for + // appointment flow api's" + // * operation. **/ + // loggingUtil.addVersionHistory( + // VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldHubData).newData(hub) + // .build()); log.info("Saved new authToken and areaCode for Hub."); return hub; @@ -419,7 +400,7 @@ public class AppointmentDao { // Parse and return the NDG response return parseNdgResponse(responseJson); } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden received while retrieving NDG. Regenerating token..."); + logForbiddenError(); // Regenerate the token and retry String newAuthorizationToken = regenerateTokenAndSave(hub); return retrieveNdgByVatNumber(vatNumber, newAuthorizationToken, hub, application); @@ -448,7 +429,7 @@ public class AppointmentDao { String responseJson = Utils.convertObjectToJson(response.getBody()); return parseVisuraResponse(responseJson); } catch (FeignException.Forbidden forbiddenException) { - log.error("403 Forbidden received while retrieving NDG. Regenerating token..."); + logForbiddenError(); // Regenerate the token and retry String newAuthorizationToken = regenerateTokenAndSave(hub); return createVisura(company, newAuthorizationToken, hub); @@ -458,6 +439,11 @@ public class AppointmentDao { } } + private static void logForbiddenError() { + + log.error("403 Forbidden received while retrieving NDG. Regenerating token..."); + } + private static AppointmentNdgRequest getAppointmentNdgRequest(String vatNumber) { AppointmentNdgRequest request = new AppointmentNdgRequest(); diff --git a/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java b/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java index 319795ec..e2ab4521 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java +++ b/src/main/java/net/gepafin/tendermanagement/util/LoggingUtil.java @@ -9,7 +9,6 @@ import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.UserActionEntity; import net.gepafin.tendermanagement.entities.UserEntity; import net.gepafin.tendermanagement.entities.VersionHistoryEntity; -import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; import net.gepafin.tendermanagement.model.request.UserActionRequest; import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.repositories.UserActionsRepository; diff --git a/src/main/java/net/gepafin/tendermanagement/util/Utils.java b/src/main/java/net/gepafin/tendermanagement/util/Utils.java index b7383161..a560b29d 100644 --- a/src/main/java/net/gepafin/tendermanagement/util/Utils.java +++ b/src/main/java/net/gepafin/tendermanagement/util/Utils.java @@ -60,6 +60,9 @@ import static org.apache.commons.lang3.StringUtils.isEmpty; public class Utils { +// @Autowired +// private static TokenProvider tokenProvider; + public static final Logger log = LoggerFactory.getLogger(Utils.class); private static final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) @@ -612,4 +615,39 @@ public class Utils { throw new RuntimeException("Failed to generate JWT token", e); } } + +// public static void setHttpServletRequestForNdgProcess(HttpServletRequest originalRequest) { +// +// // Validate original request +// if (originalRequest == null) { +// throw new IllegalArgumentException("Original request cannot be null."); +// } +// +// // Create a mock request +// Claims tokenClaims = tokenProvider.getClaimsFromToken(tokenProvider.extractTokenFromRequest(originalRequest)); +// MockHttpServletRequest mockRequest = new MockHttpServletRequest(); +// mockRequest.setRequestURI(originalRequest.getRequestURI()); +// mockRequest.setMethod(originalRequest.getMethod()); +// +// // Copy essential headers and attributes from the original request +// Enumeration headerNames = originalRequest.getHeaderNames(); +// while (headerNames.hasMoreElements()) { +// String headerName = headerNames.nextElement(); +// String headerValue = originalRequest.getHeader(headerName); +// if (headerValue != null) { +// mockRequest.addHeader(headerName, headerValue); +// } +// } +// +// // Set a specific attribute if required +// if (originalRequest.getAttribute(GepafinConstant.USER_ACTION_ID) != null) { +// mockRequest.setAttribute(GepafinConstant.USER_ACTION_ID, originalRequest.getAttribute(GepafinConstant.USER_ACTION_ID)); +// } +// +// ServletRequestAttributes attributes = new ServletRequestAttributes(mockRequest); +// RequestContextHolder.setRequestAttributes(attributes); +// // Log successful context setting +// log.info("Successfully set mock request for NDG process with URI: {}", mockRequest.getRequestURI()); +// } + } diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index ad5a3282..487c8d95 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -320,7 +320,7 @@ atleast.one.id.required=At least one of companyId or applicationId must be provi ndg.generated = NDG Generated. ndg.available = NDG Available. ndg.generation.in.progress = NDG generation is in progress. -ndg.fetch.successfully = NDG fetch successfully. +ndg.fetch.successfully = NDG fetched successfully. appointment.already.created = Appointment Already Created. ndg.not.found.for.this.application.or.invalid = Ndg not found for this application or invalid. provide.valid.application.document.id = Provide valid application document id. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index c7d29ceb..ac8b75d9 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -310,7 +310,7 @@ atleast.one.id.required=Almeno uno tra companyId o applicationId deve essere for #Appointment flow messages ndg.available = NDG disponibile. ndg.generation.in.progress = La generazione NDG ? in corso. -ndg.fetch.successfully = Recupero NDG riuscito. +ndg.fetch.successfully = NDG recuperato con successo. appointment.already.created = Appuntamento gi? creato. ndg.not.found.for.this.application.or.invalid = NDG non trovato per questa applicazione o non valido. provide.valid.application.document.id = Fornisci un ID documento applicativo valido.