package net.gepafin.tendermanagement.dao; import com.amazonaws.services.s3.AmazonS3Client; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import feign.FeignException; import io.jsonwebtoken.Claims; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.config.jwt.TokenProvider; import net.gepafin.tendermanagement.constants.AppointmentApiConstant; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.ApplicationAmendmentRequestEntity; import net.gepafin.tendermanagement.entities.ApplicationEntity; import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity; import net.gepafin.tendermanagement.entities.CompanyEntity; import net.gepafin.tendermanagement.entities.DocumentEntity; import net.gepafin.tendermanagement.entities.HubEntity; import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum; import net.gepafin.tendermanagement.enums.DocumentSourceTypeEnum; import net.gepafin.tendermanagement.enums.NotificationTypeEnum; import net.gepafin.tendermanagement.enums.VersionActionTypeEnum; import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest; import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest; import net.gepafin.tendermanagement.model.request.AppointmentVisuraListRequest; import net.gepafin.tendermanagement.model.request.AppointmentVisuraRequest; import net.gepafin.tendermanagement.model.request.CreateAppointmentRequest; import net.gepafin.tendermanagement.model.request.UploadDocToExternalSystemRequest; import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.model.response.AppointmentCreationResponse; import net.gepafin.tendermanagement.model.response.AppointmentLoginResponse; import net.gepafin.tendermanagement.model.response.DocumentUploadResponse; import net.gepafin.tendermanagement.model.response.NdgResponse; import net.gepafin.tendermanagement.repositories.ApplicationRepository; import net.gepafin.tendermanagement.repositories.CompanyRepository; import net.gepafin.tendermanagement.repositories.DocumentRepository; import net.gepafin.tendermanagement.repositories.HubRepository; import net.gepafin.tendermanagement.repositories.UserRepository; import net.gepafin.tendermanagement.service.AmazonS3Service; import net.gepafin.tendermanagement.service.ApplicationService; import net.gepafin.tendermanagement.service.CompanyService; import net.gepafin.tendermanagement.service.feignClient.AppointmentApiService; import net.gepafin.tendermanagement.service.impl.ApplicationEvaluationServiceImpl; import net.gepafin.tendermanagement.util.LoggingUtil; import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException; import net.gepafin.tendermanagement.web.rest.api.errors.Status; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @Slf4j @Component public class AppointmentDao { @Value("${appointment.portal.user}") private String user; @Value("${appointment.portal.password}") private String password; @Value("${appointment.portal.source}") private String source; @Value("${appointment.portal.context}") private String context; @Value("${default.hub.uuid}") private String defaultHubUuid; @Value("${aws.s3.url}") private String s3Url; @Value("${aws.s3.bucket.name}") private String OLD_BUCKET; @Autowired private HubRepository hubRepository; @Autowired private AppointmentApiService appointmentApiService; @Autowired private ApplicationService applicationService; @Autowired private CompanyService companyService; @Autowired private ApplicationRepository applicationRepository; @Autowired private CompanyRepository companyRepository; @Autowired private DocumentDao documentDao; @Autowired private AmazonS3Client s3Client; @Autowired private DocumentRepository documentRepository; @Autowired private HttpServletRequest request; @Autowired private LoggingUtil loggingUtil; @Autowired private TokenProvider tokenProvider; @Autowired private NotificationDao notificationDao; @Autowired private UserRepository userRepository; @Autowired private ApplicationEvaluationServiceImpl applicationEvaluationService; @Autowired private AmazonS3Service amazonS3Service; @Autowired private ApplicationDao applicationDao; @Autowired private ApplicationAmendmentRequestDao applicationAmendmentRequestDao; @Autowired private ApplicationEvaluationDao applicationEvaluationDao; private final Map executorMap = new ConcurrentHashMap<>(); private final ConcurrentHashMap threadForDocumentMap = new ConcurrentHashMap<>(); private static final ThreadLocal threadLocalHubId = new ThreadLocal<>(); public NdgResponse checkNdgForAppointment(Long applicationId) { log.info("Starting NDG check for appointment. applicationId: {}", applicationId); ApplicationEntity application = applicationService.validateApplication(applicationId); NdgResponse ndgResponse = new NdgResponse(); if (application.getNdgStatus() != null && application.getNdgStatus().equalsIgnoreCase(GepafinConstant.NDG_IN_PROGRESS)) { log.warn("NDG generation already in progress. applicationId: {}", applicationId); 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 log.info("Updating NDG status to IN_PROGRESS. applicationId: {}", applicationId); application.setNdgStatus(GepafinConstant.NDG_IN_PROGRESS); applicationRepository.save(application); // Start async processing HubEntity hub = hubRepository.findByHubId(application.getHubId()); loginToOdessa(hub, application); startAsyncNdgProcessing(applicationId); log.info("NDG check initiation completed. applicationId: {}", applicationId); return ndgResponse; } // private HubEntity loginToOdessa(HubEntity hub, ApplicationEntity application) { // // int maxRetries = 3; // int attempt = 0; // boolean success = false; // while (attempt < maxRetries && !success) { // attempt++; // 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(); // log.info("Got the auth for login to odessa {}", authJwtToken); // hub.setAuthToken(authJwtToken); // hubRepository.save(hub); // Map body = Collections.emptyMap(); // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); // if (responseLogin.getStatusCode() == HttpStatus.OK) { // log.info("Login successful to odessa. Parsing response."); // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); // // // Validate and save token // if (parsedResponse.getTokenId() != null) { // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); // hub.setAreaCode(parsedResponse.getAreaCode()); // hubRepository.save(hub); // log.info("Saved new authToken and areaCode for Hub."); // success = true; // return hub; // } else { // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); // } // } // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); // } catch (FeignException.Forbidden forbiddenException) { // log.error("Failed to login to odessa due to some error"); // // // Extract raw response body // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response // // // Parse JSON to check for "PasswordExpired" // try { // ObjectMapper objectMapper = new ObjectMapper(); // JsonNode rootNode = objectMapper.readTree(responseBody); // JsonNode errorsNode = rootNode.path("errors"); // // if (errorsNode.isArray()) { // for (JsonNode error : errorsNode) { // // Check the main errorCode // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { // application.setNdgStatus(GepafinConstant.NDG_FAILED); // applicationRepository.save(application); // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); // } // // // Check inside "subErrors" // JsonNode subErrorsNode = error.path("subErrors"); // if (subErrorsNode.isArray()) { // for (JsonNode subError : subErrorsNode) { // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { // application.setNdgStatus(GepafinConstant.NDG_FAILED); // applicationRepository.save(application); // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); // } // } // } // } // } // } catch (IOException e) { // log.error("Error parsing JSON response: {}", e.getMessage()); // } // } catch (Exception e) { // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); // throw new RuntimeException("Authentication failed on Odessa. try again", e); // } // } // return null; // } // // // private HubEntity authenticateAndSaveToken(HubEntity hub) { // // int maxRetries = 3; // int attempt = 0; // boolean success = false; // while (attempt < maxRetries && !success) { // attempt++; // 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(); // log.info("Got the auth for login to odessa {}", authJwtToken); // hub.setAuthToken(authJwtToken); // hubRepository.save(hub); // // Prepare the request body (adjust if necessary for login API) // Map body = Collections.emptyMap(); // // Perform login API call // ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); // // // Handle successful login // if (responseLogin.getStatusCode() == HttpStatus.OK) { // log.info("Login successful to odessa. Parsing response."); // String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); // AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); // // // Validate and save token // if (parsedResponse.getTokenId() != null) { // hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); // hub.setAreaCode(parsedResponse.getAreaCode()); // hubRepository.save(hub); // // log.info("Saved new authToken and areaCode for Hub."); // success = true; // return hub; // } else { // throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); // } // } // // Handle non-OK response // throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); // } catch (FeignException.Forbidden forbiddenException) { // log.error("Failed to login to odessa due to some error occurred."); // // // Extract raw response body // String responseBody = forbiddenException.contentUTF8(); // Extract raw JSON response // // // Parse JSON to check for "PasswordExpired" // try { // ObjectMapper objectMapper = new ObjectMapper(); // JsonNode rootNode = objectMapper.readTree(responseBody); // JsonNode errorsNode = rootNode.path("errors"); // // if (errorsNode.isArray()) { // for (JsonNode error : errorsNode) { // // Check the main errorCode // if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); // } // // // Check inside "subErrors" // JsonNode subErrorsNode = error.path("subErrors"); // if (subErrorsNode.isArray()) { // for (JsonNode subError : subErrorsNode) { // if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { // throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); // } // } // } // } // } // } catch (IOException e) { // log.error("Error parsing JSON response: {}", e.getMessage()); // } // } catch (Exception e) { // log.error("Failed to authenticate user on Odessa : {}", e.getMessage(), e); // throw new RuntimeException("Authentication failed on Odessa. try again", e); // } // } // return null; // } private void loginToOdessa(HubEntity hub, ApplicationEntity application) { log.info("Starting login to Odessa. HubId: {}, ApplicationId: {}", hub.getId(), application.getId()); performOdessaLogin(hub, application); } private HubEntity authenticateAndSaveToken(HubEntity hub, ApplicationEntity application) { return performOdessaLogin(hub, application); } private HubEntity performOdessaLogin(HubEntity hub, ApplicationEntity application) { int maxRetries = 3; int attempt = 0; while (attempt < maxRetries) { attempt++; try { String authJwtToken = Utils.generateAuthTokenForLoginToOdessa(); log.info("Got the auth for login to odessa {}", authJwtToken); hub.setAuthToken(authJwtToken); hubRepository.save(hub); Map body = Collections.emptyMap(); ResponseEntity responseLogin = appointmentApiService.loginWithOdessa(authJwtToken, source, context, user, password, body); if (responseLogin.getStatusCode() == HttpStatus.OK) { log.info("Login to Odessa successful. Parsing response. HubId: {}", hub.getId()); String loginResponseJson = Utils.convertObjectToJson(responseLogin.getBody()); AppointmentLoginResponse parsedResponse = parseLoginResponse(loginResponseJson); if (parsedResponse.getTokenId() != null) { hub.setAppointmentAuthTokenId(parsedResponse.getTokenId()); hub.setAreaCode(parsedResponse.getAreaCode()); hubRepository.save(hub); log.info("Saved new authToken and areaCode for Hub."); return hub; } else { log.error("Login response from Odessa missing tokenId. HubId: {}", hub.getId()); throw new RuntimeException("Login response is missing a valid tokenId for login to odessa system, please try again."); } } throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_IN_GENERATING_NDG_TRY_AGAIN)); } catch (FeignException.Forbidden forbiddenException) { log.error("Failed to login to odessa due to forbidden error."); CheckPasswordExpiredOrErrorInResponse(application, forbiddenException); } catch (Exception e) { log.error("Failed to authenticate user on Odessa (Attempt {}): {}", attempt, e.getMessage(), e); } } throw new RuntimeException("Max retries exceeded. Failed to login to Odessa."); } private void CheckPasswordExpiredOrErrorInResponse(ApplicationEntity application, FeignException.Forbidden forbiddenException) { String responseBody = forbiddenException.contentUTF8(); try { ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = objectMapper.readTree(responseBody); JsonNode errorsNode = rootNode.path("errors"); if (errorsNode.isArray()) { for (JsonNode error : errorsNode) { if (GepafinConstant.PASSWORD_EXPIRED.equals(error.path("errorCode").asText())) { if (application != null) { application.setNdgStatus(GepafinConstant.NDG_FAILED); applicationRepository.save(application); } throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); } JsonNode subErrorsNode = error.path("subErrors"); if (subErrorsNode.isArray()) { for (JsonNode subError : subErrorsNode) { if (GepafinConstant.PASSWORD_EXPIRED.equals(subError.path("errorCode").asText())) { if (application != null) { application.setNdgStatus(GepafinConstant.NDG_FAILED); applicationRepository.save(application); } throw new CustomValidationException(Status.FORBIDDEN, Translator.toLocale(GepafinConstant.PASSWORD_EXPIRED_LOGIN_TO_ODESSA)); } } } } } } catch (IOException e) { log.error("Error parsing JSON response: {}", e.getMessage()); } catch (Exception e) { throw new RuntimeException("Authentication failed on Odessa. try again", e); } } 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 log.info("Starting NDG generation process for applicationId: {}", applicationId); 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 { // Authenticate and fetch token if required if (hub.getAppointmentAuthTokenId() == null || hub.getAreaCode() == null) { authenticateAndSaveToken(hub, application); } String authorizationToken = getBearerToken(hub); // Try retrieving NDG by VAT number AppointmentLoginResponse ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application); if (isNdgValid(ndgResponse.getNdg())) { saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); log.info("NDG successfully generated for applicationId: {}", applicationId); } else { // If NDG isn't immediately available, start polling handleNdgPolling(application, company, hub, authorizationToken); } } catch (Exception e) { log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", applicationId, company.getId(), hub.getId(), e.getMessage(), e); } } private void handleNdgPolling(ApplicationEntity application, CompanyEntity company, HubEntity hub, String authorizationToken) { try { log.info("Starting NDG polling for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(),company.getId(), hub.getId()); long startTime = System.currentTimeMillis(); while (true) { if (application.getNdg() != null) { log.info("NDG retrieved for applicationId: {}", application.getId()); break; } try { // Fetch Ndg via creating visura AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); if (isNdgValid(visuraResponse.getNdg())) { log.info("Valid NDG retrieved from create visura api response: {} | ApplicationId: {}", visuraResponse.getNdg(), application.getId()); company.setNdg(visuraResponse.getNdg()); application.setNdg(visuraResponse.getNdg()); application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); application.setIdVisura(visuraResponse.getIdVisura()); applicationRepository.save(application); companyRepository.save(company); ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation( application.getApplicationEvaluationId()); Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); log.info("Got NDG and saved successfully for applicationId: {}", application.getId()); break; } else { // Fetch Visura list and attempt to parse NDG String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); log.debug("Parsing NDG from visura list response | ApplicationId: {}", application.getId()); String ndg = parseNdgFromVisuraListResponse(visuraListJson); if (isNdgValid(ndg)) { // CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); // ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); log.info("Valid NDG retrieved: {} | ApplicationId: {}", ndg, application.getId()); company.setNdg(ndg); application.setNdg(ndg); application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); application.setIdVisura(visuraResponse.getIdVisura()); applicationRepository.save(application); companyRepository.save(company); ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation( application.getApplicationEvaluationId()); Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); log.info("NDG saved successfully for applicationId: {}", application.getId()); 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()); } } private static String getBearerToken(HubEntity hub) { return "Bearer " + hub.getAppointmentAuthTokenId(); } private boolean isNdgValid(String ndg) { return ndg != null && !ndg.isEmpty(); } private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { application.setNdg(ndg); application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); company.setNdg(ndg); companyRepository.save(company); applicationRepository.save(application); ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); // Map placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); Map placeHolders = new HashMap<>(); placeHolders.put("{{call_name}}", application.getCall().getName()); placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); log.info("NDG saved for applicationId: {}, {}", application.getId(), application.getNdg()); } private String getVisuraList(String idVisura, String authorizationToken, ApplicationEntity application, HubEntity hub) { log.info("Initiating Visura list retrieval | ApplicationId: {}, HubId: {}, IdVisura: {}", application.getId(), hub.getId(), idVisura); AppointmentVisuraListRequest visuraListRequest = new AppointmentVisuraListRequest(); AppointmentVisuraListRequest.VisuraFilter filter = new AppointmentVisuraListRequest.VisuraFilter(); filter.setIdVisura(idVisura); visuraListRequest.setFilter(filter); try { String requestJson = Utils.convertObjectToJson(visuraListRequest); ResponseEntity response = appointmentApiService.getVisuraList(requestJson, authorizationToken); return Utils.convertObjectToJson(response.getBody()); } catch (FeignException.Forbidden forbiddenException) { log.warn("403 Forbidden while fetching Visura list. Attempting token regeneration | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); // Regenerate the token and retry String newAuthorizationToken = regenerateTokenAndSave(hub, application); return getVisuraList(idVisura, newAuthorizationToken, application, hub); } catch (Exception e) { log.error("Error while fetching Visura list | ApplicationId: {}, HubId: {}, Error: {}", application.getId(), hub.getId(), e.getMessage(), e); throw new RuntimeException("Error fetching Ndg List", e); } } private AppointmentLoginResponse retrieveNdgByVatNumber(String vatNumber, String authorizationToken, HubEntity hub, ApplicationEntity application) { try { log.info("Initiating NDG retrieval by VAT number | ApplicationId: {}, HubId: {}, VAT: {}", application.getId(), hub.getId(), vatNumber); // Prepare the NDG request AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber); // Call the API to retrieve NDG ResponseEntity response = appointmentApiService.getNdgByVatNumber(ndgRequest, authorizationToken); String responseJson = Utils.convertObjectToJson(response.getBody()); // Parse and return the NDG response return parseNdgResponse(responseJson); } catch (FeignException.Forbidden forbiddenException) { log.error("403 Forbidden during NDG retrieval | ApplicationId: {}, HubId: {}", application.getId(), hub.getId()); logForbiddenError(); // Regenerate the token and retry String newAuthorizationToken = regenerateTokenAndSave(hub, application); return retrieveNdgByVatNumber(vatNumber, newAuthorizationToken, hub, application); } catch (Exception e) { log.error("Error during NDG retrieval | ApplicationId: {}, HubId: {}, Message: {}", application.getId(), hub.getId(), e.getMessage(), e); throw new RuntimeException("NDG retrieval failed.", e); } } private String regenerateTokenAndSave(HubEntity hub, ApplicationEntity application) { hub = authenticateAndSaveToken(hub, application); return "Bearer " + hub.getAppointmentAuthTokenId(); } private AppointmentLoginResponse createVisura(CompanyEntity company, String authorizationToken, HubEntity hub) { try { String visuraRequest = getAppointmentVisuraRequest(company, hub.getAreaCode()); ResponseEntity response = appointmentApiService.createVisura(visuraRequest, authorizationToken); String responseJson = Utils.convertObjectToJson(response.getBody()); return parseVisuraResponse(responseJson); } catch (FeignException.Forbidden forbiddenException) { logForbiddenError(); // Regenerate the token and retry String newAuthorizationToken = regenerateTokenAndSave(hub, null); return createVisura(company, newAuthorizationToken, hub); } catch (Exception e) { log.error("Failed to create Visura for Ndg : {}", e.getMessage()); throw new RuntimeException("Visura creation failed for Ndg.", e); } } private static void logForbiddenError() { log.error("403 Forbidden received while retrieving NDG. Regenerating token..."); } private static AppointmentNdgRequest getAppointmentNdgRequest(String vatNumber) { log.info("Creating Appointment NDG Request | VAT Number: {}", vatNumber); AppointmentNdgRequest request = new AppointmentNdgRequest(); AppointmentNdgRequest.Filter filter = new AppointmentNdgRequest.Filter(); filter.setPartitaIva(vatNumber); AppointmentNdgRequest.Pagination pagination = new AppointmentNdgRequest.Pagination(); pagination.setTargetPage(AppointmentApiConstant.TARGET_PAGE_SIZE); pagination.setRecordsPerPage(AppointmentApiConstant.RECORD_PER_PAGE_SIZE); request.setFilter(filter); request.setPagination(pagination); return request; } private static String getAppointmentVisuraRequest(CompanyEntity company, String areaCode) { AppointmentVisuraRequest visuraRequest = new AppointmentVisuraRequest(); AppointmentVisuraRequest.VisuraInput input = new AppointmentVisuraRequest.VisuraInput(); input.setPartitaIva(company.getVatNumber()); input.setCodiceFiscale(company.getCodiceFiscale()); input.setCodArea(areaCode); input.setVisuraMode(AppointmentApiConstant.VISURA_MODE); input.setVisuraProvider(AppointmentApiConstant.VISURA_PROVIDER); input.setCodAgente(AppointmentApiConstant.COD_AGENTE); input.setAnagraficaLegame(AppointmentApiConstant.IS_ANAGRAFICA_LEGAME); input.setCreaAnagrafica(AppointmentApiConstant.CREA_ANAGRAFICA); input.setFromRating(AppointmentApiConstant.IS_FROM_RATING); input.setSalvaDocumenti(AppointmentApiConstant.SALVA_DOCUMENTI); input.setVisuraType(AppointmentApiConstant.VISURA_TYPE); visuraRequest.setInput(input); return Utils.convertObjectToJson(visuraRequest); } private String parseNdgFromVisuraListResponse(String jsonResponse) { try { ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = objectMapper.readTree(jsonResponse); JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); if (dataNode != null && dataNode.isArray() && dataNode.size() > 0) { JsonNode firstEntry = dataNode.get(0); JsonNode ndgClienteNode = firstEntry.get("ndgCliente"); if (ndgClienteNode != null && ndgClienteNode.get("code") != null) { String code = ndgClienteNode.get("code").asText(); return normalizeNullValue(code); } } log.warn("NDG not found in Visura List API response."); return null; } catch (Exception e) { log.error("Failed to parse NDG from Visura List API response: {}", e.getMessage(), e); throw new RuntimeException("Error parsing NDG from Visura List API response", e); } } public AppointmentLoginResponse parseLoginResponse(String jsonResponse) { try { ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = objectMapper.readTree(jsonResponse); JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); if (dataNode != null) { AppointmentLoginResponse response = new AppointmentLoginResponse(); response.setTokenId(dataNode.get("tokenId").asText()); JsonNode areasNode = dataNode.get("areas"); if (areasNode != null && areasNode.isArray() && areasNode.size() > 0) { response.setAreaCode(areasNode.get(0).get("code").asText()); } response.setCompanyId(dataNode.get("companyId").asLong()); return response; } else { throw new RuntimeException("Invalid JSON structure: Missing 'data' node."); } } catch (Exception e) { throw new RuntimeException("Failed to parse response from loginApi for odessa: " + e.getMessage(), e); } } public AppointmentLoginResponse parseVisuraResponse(String jsonResponse) { try { // Log full raw JSON for debug purposes log.info("Raw Visura JSON Response: {}", jsonResponse); ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = objectMapper.readTree(jsonResponse); JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); if (dataNode != null && dataNode.isObject()) { AppointmentLoginResponse response = new AppointmentLoginResponse(); JsonNode idVisuraNode = dataNode.get(GepafinConstant.ID_VISURA_STRING); JsonNode ndgNode = dataNode.get(GepafinConstant.NDG_STRING); if (idVisuraNode == null || ndgNode == null) { log.error("Missing expected fields in 'data' node. JSON: {}", dataNode); } response.setIdVisura(normalizeNullValue(idVisuraNode != null ? idVisuraNode.asText() : null)); response.setNdg(normalizeNullValue(ndgNode != null ? ndgNode.asText() : null)); return response; } else { System.err.println("Invalid JSON: 'data' node is missing or not an object."); throw new RuntimeException("Invalid JSON structure: Missing or malformed 'data' node."); } } catch (Exception e) { System.err.println("Exception while parsing Visura response: " + e.getMessage()); throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); } } public AppointmentLoginResponse parseNdgResponse(String jsonResponse) { try { ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = objectMapper.readTree(jsonResponse); JsonNode dataArray = rootNode.get(GepafinConstant.DATA_STRING); if (dataArray == null || !dataArray.isArray() || dataArray.isEmpty()) { log.info("NDG data is empty or missing in the response."); AppointmentLoginResponse emptyResponse = new AppointmentLoginResponse(); emptyResponse.setNdg(null); return emptyResponse; } JsonNode firstDataEntry = dataArray.get(0); AppointmentLoginResponse response = new AppointmentLoginResponse(); if (firstDataEntry.has(GepafinConstant.NDG_STRING)) { response.setNdg(normalizeNullValue(firstDataEntry.get(GepafinConstant.NDG_STRING).asText())); } return response; } catch (Exception e) { log.error("Failed to parse response: {}", e.getMessage(), e); throw new RuntimeException("Failed to parse NDG response.", e); } } private String normalizeNullValue(String value) { return (value == null || GepafinConstant.NULL_STRING.equalsIgnoreCase(value.trim())) ? null : value; } public AppointmentCreationResponse createAppointment(Long applicationId, CreateAppointmentRequest createAppointmentRequest) { // Validate the application log.info("Starting appointment creation for applicationId: {}", applicationId); ApplicationEntity application = applicationService.validateApplication(applicationId); AppointmentCreationResponse appointmentCreationResponse = new AppointmentCreationResponse(); ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); HubEntity hub = hubRepository.findByHubId(application.getHubId()); // Check hub UUID and enforce constraints if (!hub.getUniqueUuid().equals(defaultHubUuid)) { log.info("Appointment cannot be created for another Hub; default is required for Gepafin."); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_APPOINTMENT_FOR_ANOTHER_HUB)); } try { // Pre-check conditions for appointment creation if (application.getNdg() != null && !Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS) && application.getAppointmentId() != null) { appointmentCreationResponse.setAppointmentId(application.getAppointmentId()); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_ALREADY_CREATED)); // return appointmentCreationResponse; } if (application.getNdg() == null && Objects.equals(application.getNdgStatus(), GepafinConstant.NDG_IN_PROGRESS)) { log.warn("NDG in progress but not available for applicationId: {}", applicationId); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NDG_NOT_FOUND_FOR_APPLICATION)); } // Generate authorization token and fetch template data String authorizationToken = regenerateTokenAndSave(hub, application); Long appointmentTemplateId = application.getCall().getAppointmentTemplateId(); if (appointmentTemplateId == null) { log.error("Missing appointment template ID for applicationId: {}", applicationId); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_CANNOT_BE_CREATED)); } ResponseEntity response = appointmentApiService.getAppointmentTemplateForTemplateCreation(authorizationToken, appointmentTemplateId); if (response.getStatusCode() != HttpStatus.OK) { log.error("Failed to retrieve appointment template for appointment creation. Status: {}", response.getStatusCode()); throw new IllegalStateException("Failed to retrieve appointment template for appointment creation"); } // Parse template data String responseDataForTemplate = Utils.convertObjectToJson(response.getBody()); AppointmentCreationRequest templateRichiestaData = parseTemplateResponseData(responseDataForTemplate); // Build the appointment request body AppointmentCreationRequest appointmentCreationRequest = buildAppointmentCreationRequest(applicationId, createAppointmentRequest, appointmentTemplateId, templateRichiestaData); log.info("AppointmentCreationRequest : {}", appointmentCreationRequest); String appointmentRequestBody = Utils.convertObjectToJson(appointmentCreationRequest); // Make API call to create the appointment log.info("Context:{}, Authorization Token : {}, RequestBody : {}", context, authorizationToken, appointmentRequestBody); ResponseEntity appointmentResponse = appointmentApiService.createAppointment(authorizationToken, context, appointmentRequestBody); String appointmentId = extractAppointmentIdFromResponse(appointmentResponse); if (appointmentId == null) { log.error("Failed to extract appointment ID from response for applicationId: {}", applicationId); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.APPOINTMENT_NOT_CREATED)); } // Update application with the appointment ID application.setAppointmentId(appointmentId); application.setStatus(ApplicationStatusTypeEnum.APPOINTMENT.getValue()); applicationRepository.save(application); // Log version history loggingUtil.addVersionHistory( VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplicationData).newData(application).build()); appointmentCreationResponse.setAppointmentId(appointmentId); return appointmentCreationResponse; } catch (FeignException.Forbidden forbiddenException) { log.error("403 Forbidden received while retrieving template. Attempting to regenerate token and retry. Application ID: {}", applicationId); regenerateTokenAndSave(hub, application); return createAppointment(applicationId, createAppointmentRequest); } } private String extractAppointmentIdFromResponse(ResponseEntity appointmentResponse) { if (appointmentResponse.getBody() != null) { log.info("Appointment API Response : {}", appointmentResponse.getBody()); try { Map responseBody = (Map) appointmentResponse.getBody(); // 1. Try to get appointment ID from data.id if (responseBody.containsKey(GepafinConstant.DATA_STRING)) { Map data = (Map) responseBody.get(GepafinConstant.DATA_STRING); if (data != null && data.containsKey(GepafinConstant.ID_STRING)) { return data.get(GepafinConstant.ID_STRING).toString(); } } // 2. If ID not present, check errors[0].cause.errorDescription if (responseBody.containsKey(GepafinConstant.ERROR_STRING)) { List> errors = (List>) responseBody.get(GepafinConstant.ERROR_STRING); if (errors != null && !errors.isEmpty()) { Map firstError = errors.get(0); if (firstError.containsKey(GepafinConstant.CAUSE_STRING)) { Map cause = (Map) firstError.get(GepafinConstant.CAUSE_STRING); if (cause != null && cause.containsKey(GepafinConstant.ERROR_DESCRIPTION_STRING)) { String errorDescription = cause.get(GepafinConstant.ERROR_DESCRIPTION_STRING).toString(); log.warn("Appointment creation failed: {}", errorDescription); } } } } } catch (Exception e) { log.error("Error while extracting appointment ID or parsing error message", e); } } return null; } public AppointmentCreationRequest parseTemplateResponseData(String jsonResponse) { try { ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = objectMapper.readTree(jsonResponse); JsonNode richiesteClienteArray = rootNode.path(GepafinConstant.DATA_STRING).path(GepafinConstant.RICHIESTE_CLIENTE_STRING); // Initialize the result object AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); List richiestaClienteList = new ArrayList<>(); if (!richiesteClienteArray.isArray()) { log.warn("richiesteCliente array is missing or not an array."); return new AppointmentCreationRequest(); } for (JsonNode richiestaNode : richiesteClienteArray) { if (richiestaNode.isNull()) continue; AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); JsonNode prodottoNode = richiestaNode.path(AppointmentApiConstant.PRODOTTO); String prodottoCode = prodottoNode.path(AppointmentApiConstant.PRODOTTO_CODE).asText(); richiestaCliente.setCodProdotto(prodottoCode); richiestaCliente.setIdMotivazione(getIntValue(richiestaNode)); richiestaCliente.setCodAbi(getTextValue(richiestaNode, AppointmentApiConstant.COD_ABI)); richiestaCliente.setCodCab(getTextValue(richiestaNode, AppointmentApiConstant.COD_CAB)); richiestaCliente.setIdNota(getTextValue(richiestaNode, AppointmentApiConstant.ID_NOTA)); richiestaCliente.setImportoAgevolato(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_AGEVOLATO)); richiestaCliente.setImportoMedioLungoTermine(getTextValue(richiestaNode, AppointmentApiConstant.IMPORTO_MEDIOLUNGO_TERMINE)); richiestaCliente.setCodTipoProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_TIPO_PRODOTTO)); richiestaCliente.setCodCategoriaProdotto(getTextValue(richiestaNode, AppointmentApiConstant.COD_CATEGORIA_PRODOTTO)); richiestaCliente.setCodFormaTecnica(getTextValue(richiestaNode, AppointmentApiConstant.COD_FORMATECNICA)); richiestaCliente.setCodOperazione(getTextValue(richiestaNode, AppointmentApiConstant.COD_OPERAZIONE)); richiestaClienteList.add(richiestaCliente); } input.setRichiestaCliente(richiestaClienteList); appointmentCreationRequest.setInput(input); return appointmentCreationRequest; } catch (JsonProcessingException e) { log.error("JSON processing error: {}", e.getMessage(), e); throw new IllegalStateException("Invalid JSON structure in template response", e); } } private String getTextValue(JsonNode node, String fieldName) { return node.path(fieldName).isTextual() ? node.path(fieldName).asText() : null; } private int getIntValue(JsonNode node) { return node.path(AppointmentApiConstant.MOTIVAZIONE).path(AppointmentApiConstant.MOTIVAZIONE_ID).asInt(); } public AppointmentCreationRequest buildAppointmentCreationRequest(Long applicationId, CreateAppointmentRequest createAppointmentRequest, Long areaCode, AppointmentCreationRequest templateRichiestaData) { ApplicationEntity application = applicationService.validateApplication(applicationId); CreateAppointmentRequest.Nota nota = createAppointmentRequest.getNota(); AppointmentCreationRequest appointmentCreationRequest = new AppointmentCreationRequest(); AppointmentCreationRequest.Input input = new AppointmentCreationRequest.Input(); // Set Input Fields input.setId(areaCode); input.setNdg(application.getNdg()); // Populate richiestaCliente from template data List richiestaClienteList = new ArrayList<>(); for (AppointmentCreationRequest.RichiestaCliente templateRichiesta : templateRichiestaData.getInput().getRichiestaCliente()) { AppointmentCreationRequest.RichiestaCliente richiestaCliente = new AppointmentCreationRequest.RichiestaCliente(); BeanUtils.copyProperties(templateRichiesta, richiestaCliente); // Add specific `nota` AppointmentCreationRequest.Nota requestNota = new AppointmentCreationRequest.Nota(); requestNota.setTitolo(nota.getTitolo()); requestNota.setTesto(nota.getTesto()); richiestaCliente.setNota(requestNota); richiestaCliente.setDurataMesiFinanziamento(createAppointmentRequest.getDurataMesiFinanziamento()); richiestaCliente.setImportoBreveTermine(createAppointmentRequest.getImportoBreveTermine()); richiestaClienteList.add(richiestaCliente); } input.setRichiestaCliente(richiestaClienteList); appointmentCreationRequest.setInput(input); return appointmentCreationRequest; } public DocumentUploadResponse uploadDocumentToExternalSystem(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest) { log.info("Initiating upload to external system for documentId: {}", documentId); // Check if the document is already being processed DocumentEntity systemDoc = documentDao.validateDocument(documentId); ApplicationEntity application = null; if (systemDoc != null) { DocumentSourceTypeEnum sourceType = DocumentSourceTypeEnum.valueOf(systemDoc.getSource()); switch (sourceType) { case APPLICATION: application = applicationDao.validateApplication(systemDoc.getSourceId()); break; case AMENDMENT: ApplicationAmendmentRequestEntity applicationAmendmentEntity = applicationAmendmentRequestDao.validateApplicationAmendmentRequest(systemDoc.getSourceId()); application = applicationDao.validateApplication(applicationAmendmentEntity.getApplicationId()); break; case EVALUATION: ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationDao.validateApplicationEvaluation(systemDoc.getSourceId()); application = applicationDao.validateApplication(applicationEvaluationEntity.getApplicationId()); break; case CALL: break; default: log.warn("Unhandled document source type: {}", sourceType); break; } } Claims claims = tokenProvider.getClaimsFromToken(tokenProvider.extractTokenFromRequest(request)); Long hubId = Utils.extractHubIdFromPayload(claims.getSubject()); // Authenticate the hub before proceeding HubEntity hub = hubRepository.findByHubId(hubId); authenticateAndSaveToken(hub, application); if (systemDoc != null && systemDoc.getDocumentAttachmentId() != null) { // If the documentAttachmentId is already set, return the response log.info("Document already uploaded with documentAttachmentId: {}", systemDoc.getDocumentAttachmentId()); DocumentUploadResponse response = new DocumentUploadResponse(); response.setDocumentAttachmentId(systemDoc.getDocumentAttachmentId()); return response; } // Check if a thread is already running for this document upload if (threadForDocumentMap.containsKey(documentId)) { log.warn("Document upload already running for documentId: {}", documentId); throw new CustomValidationException(Status.SUCCESS, Translator.toLocale(GepafinConstant.DOCUMENT_UPLOADING_IN_PROGRESS)); } // Start the upload process in the background ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { Thread thread = new Thread(runnable); thread.setName(GepafinConstant.ASYNC_DOCUMENT_UPLOAD_NAME + documentId); return thread; }); threadForDocumentMap.put(documentId, executor); ApplicationEntity finalApplication = application; executor.submit(() -> { threadLocalHubId.set(hubId); try { log.info("Starting async document upload for documentId: {}", documentId); uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, finalApplication); } catch (Exception e) { log.error("Error in async document upload for documentId: {}", documentId, e); } finally { // Cleanup resources ExecutorService executorToShutdown = threadForDocumentMap.remove(documentId); if (executorToShutdown != null) { executorToShutdown.shutdown(); threadLocalHubId.remove(); } log.info("Async document upload completed for documentId: {}", documentId); } }); return null; } private void uploadDocumentToExternalSystemSync(Long documentId, UploadDocToExternalSystemRequest docToExternalSystemRequest, ApplicationEntity application) { // Synchronous upload logic DocumentEntity systemDoc = documentDao.validateDocument(documentId); Long hubId = threadLocalHubId.get(); HubEntity hub = hubRepository.findByHubId(hubId); if (!hub.getUniqueUuid().equals(defaultHubUuid)) { log.info("Document cannot be uploaded for another Hub, it is default for Gepafin."); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.NO_DOCUMENT_UPLOAD_FOR_ANOTHER_HUB)); } log.info("Got Document in system: {}", systemDoc); String oldUrl = systemDoc.getFilePath(); String authorizationToken = getBearerToken(hub); try { File localFile = downloadFileFromS3(oldUrl); MultipartFile multipartFile = convertFileToMultipartFile(localFile); UploadDocToExternalSystemRequest externalSystemRequest = new UploadDocToExternalSystemRequest(); externalSystemRequest.setInput(getUploadDocumentInput(docToExternalSystemRequest)); String uploadDocRequest = Utils.convertObjectToJson(externalSystemRequest); ResponseEntity uploadedDocumentData = appointmentApiService.uploadDocumentToExternalSystemForAppointment(authorizationToken, context, uploadDocRequest, multipartFile); String responseData = Utils.convertObjectToJson(uploadedDocumentData.getBody()); DocumentUploadResponse parsedResponse = parseDocumentUploadResponse(responseData); if (parsedResponse == null) { log.error("Upload failed: parsed response is null for documentId: {}", documentId); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.ERROR_UPLOADING_DOCUMENT)); } // Save the documentAttachmentId to the database systemDoc.setDocumentAttachmentId(parsedResponse.getDocumentAttachmentId()); documentRepository.save(systemDoc); log.info("Document uploaded successfully to external system: {}", parsedResponse); } catch (FeignException.Forbidden forbiddenException) { log.error("403 Forbidden from external system during upload for documentId: {}. Retrying with new token...", documentId); regenerateTokenAndSave(hub, application); uploadDocumentToExternalSystemSync(documentId, docToExternalSystemRequest, application); } catch (Exception e) { log.error("Exception during document upload: {}", e.getMessage(), e); throw new CustomValidationException(Status.BAD_REQUEST, Translator.toLocale(GepafinConstant.EXTERNAL_DOCUMENT_UPLOAD_FAILURE_MSG)); } } private UploadDocToExternalSystemRequest.Input getUploadDocumentInput(UploadDocToExternalSystemRequest docToExternalSystemRequest) { UploadDocToExternalSystemRequest.Input input = new UploadDocToExternalSystemRequest.Input(); input.setIdTipoProtocollo(docToExternalSystemRequest.getInput().getIdTipoProtocollo()); input.setIdClassificazione(docToExternalSystemRequest.getInput().getIdClassificazione()); input.setFlagDaFirmare(docToExternalSystemRequest.getInput().getFlagDaFirmare()); input.setDescrizione(docToExternalSystemRequest.getInput().getDescrizione()); UploadDocToExternalSystemRequest.Input.Attributes attributes = new UploadDocToExternalSystemRequest.Input.Attributes(); attributes.setNdg(docToExternalSystemRequest.getInput().getAttributes().getNdg()); attributes.setEmail(docToExternalSystemRequest.getInput().getAttributes().getEmail()); input.setAttributes(attributes); return input; } public static MultipartFile convertFileToMultipartFile(File file) throws IOException { FileInputStream input = new FileInputStream(file); return new MockMultipartFile(file.getName(), file.getName(), MediaType.APPLICATION_OCTET_STREAM_VALUE, input); } private File downloadFileFromS3(String fileUrl) throws Exception { String key = amazonS3Service.extractS3KeyFromUrl(fileUrl); String fileName = extractFileName(key); String folderPath = key.substring(0, key.lastIndexOf("/")); File localFile = new File(GepafinConstant.TEMP_FILE_PATH + fileName); try (InputStream s3Stream = amazonS3Service.getFile(folderPath, key); FileOutputStream outputStream = new FileOutputStream(localFile)) { s3Stream.transferTo(outputStream); } log.info("Downloaded file from old S3 bucket: {}", key); return localFile; } private String extractFileName(String filePath) { String[] parts = filePath.split("/"); return parts[parts.length - 1]; } public DocumentUploadResponse parseDocumentUploadResponse(String jsonResponse) { try { ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootNode = objectMapper.readTree(jsonResponse); // Navigate to the "data" node JsonNode dataNode = rootNode.get(GepafinConstant.DATA_STRING); if (dataNode != null) { DocumentUploadResponse response = new DocumentUploadResponse(); // Extract "documentAttachmentId" JsonNode documentAttachmentIdNode = dataNode.get(GepafinConstant.DOCUMENT_ATTACHMENT_ID_STRING); if (documentAttachmentIdNode != null) { response.setDocumentAttachmentId(documentAttachmentIdNode.asText()); } else { throw new RuntimeException("Invalid JSON structure: Missing 'documentAttachmentId' node."); } return response; } else { return null; } } catch (Exception e) { throw new RuntimeException("Failed to parse response: " + e.getMessage(), e); } } }