Files
bflows-bandi-be/src/main/java/net/gepafin/tendermanagement/dao/AppointmentDao.java
2025-06-18 20:23:12 +05:30

1483 lines
78 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.*;
import net.gepafin.tendermanagement.enums.*;
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.*;
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.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
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.*;
import java.util.concurrent.atomic.AtomicReference;
@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;
@Autowired
private NdganagRepository ndganagRepository;
private final Map<Long, ScheduledExecutorService> executorMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, ExecutorService> threadForDocumentMap = new ConcurrentHashMap<>();
private static final ThreadLocal<Long> threadLocalHubId = new ThreadLocal<>();
public NdgResponse checkNdgForAppointment(Long applicationId) {
log.info("Starting NDG check for appointment. applicationId: {}", applicationId);
ApplicationEntity application = applicationService.validateApplication(applicationId);
ApplicationEntity oldApplication = Utils.getClonedEntityForData(application);
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(NdgStatusEnum.NDG_INITITATED.getValue());
applicationRepository.save(application);
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build());
// 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<String, Object> body = Collections.emptyMap();
// ResponseEntity<Object> 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<String, Object> body = Collections.emptyMap();
// // Perform login API call
// ResponseEntity<Object> 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<String, Object> body = Collections.emptyMap();
ResponseEntity<Object> 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);
}
log.warn("Detected PASSWORD_EXPIRED error during Odessa login. ApplicationId: {}", application.getId());
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("Unexpected exception during Odessa login.Error: {}", e.getMessage(), e);
}
}
// private void startAsyncNdgProcessing(Long applicationId) {
// // If already polling for this applicationId, do nothing:
// if (executorMap.containsKey(applicationId)) {
// log.warn("Async processing already running for applicationId: {}", applicationId);
// return;
// }
// ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
//
// ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> {
// Thread t = new Thread(runnable);
// t.setName("AsyncNdgProcessing-" + applicationId);
// return t;
// });
// executorMap.put(applicationId, scheduler);
//
// // Record the start time so we can stop after 2 hours:
// long startTime = System.currentTimeMillis();
// long twoHoursMs = TimeUnit.HOURS.toMillis(2);
// long fifteenMin = 15; // in MINUTES
//
// // We need a reference to cancel the scheduled task from inside itself when we're done:
// AtomicReference<ScheduledFuture<?>> futureRef = new AtomicReference<>();
//
// Runnable pollingTask = () -> {
// RequestContextHolder.setRequestAttributes(requestAttributes, true);
// Utils.setHttpServletRequestForThread(request,HttpMethodEnum.POST.getValue(),GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID));
// try {
// // 1) If 2 hours have already passed, mark as FAILED and shut down:
// if (System.currentTimeMillis() - startTime > twoHoursMs) {
// ApplicationEntity app = applicationService.validateApplication(applicationId);
// log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId);
// app.setNdgStatus(GepafinConstant.NDG_FAILED);
// applicationRepository.save(app);
//
// futureRef.get().cancel(false);
// shutdownScheduler(applicationId);
// return;
// }
//
// // 2) Otherwise, call processNdgGeneration once:
// processNdgGeneration(applicationId);
//
// // 3) After return, check if NDG is now set or timed out. If so, cancel & shut down:
// ApplicationEntity updated = applicationService.validateApplication(applicationId);
// if (isNdgValid(updated.getNdg())) {
// log.info("NDG found for applicationId {}. Shutting down scheduler.", applicationId);
// futureRef.get().cancel(false);
// shutdownScheduler(applicationId);
// } else if (updated.getNdgStatus() != null && updated.getNdgStatus().equals(GepafinConstant.NDG_FAILED)) {
// log.info("NDG status is NDG_FAILED for applicationId {}. Shutting down scheduler.", applicationId);
// futureRef.get().cancel(false);
// shutdownScheduler(applicationId);
// }
// // Otherwise: no NDG yet, not timed out → next run happens in 15 minutes automatically.
// } catch (Exception ex) {
// log.error("Unexpected error during scheduled polling for applicationId {}: {}", applicationId, ex.getMessage(), ex);
// try {
// ApplicationEntity checkApp = applicationService.validateApplication(applicationId);
// if (System.currentTimeMillis() - startTime > twoHoursMs) {
// log.warn("After exception, 2-hour window passed for applicationId {}. Marking NDG_FAILED.", applicationId);
// checkApp.setNdgStatus(GepafinConstant.NDG_FAILED);
// applicationRepository.save(checkApp);
//
// futureRef.get().cancel(false);
// shutdownScheduler(applicationId);
// }
// } catch (Exception ignore) {
// futureRef.get().cancel(false);
// shutdownScheduler(applicationId);
// }
// }
// };
//
// // Schedule pollingTask: run now (delay=0), then every fifteen minutes:
// ScheduledFuture<?> future = scheduler.scheduleWithFixedDelay(pollingTask, 0, // initial delay = 0 min → run immediately
// fifteenMin, // subsequent runs every 15 minutes
// TimeUnit.MINUTES);
// futureRef.set(future);
// }
private void shutdownScheduler(Long applicationId) {
ScheduledExecutorService shed = executorMap.remove(applicationId);
if (shed != null) {
shed.shutdownNow();
}
log.info("Scheduler shut down for applicationId: {}", applicationId);
}
private void processNdgGeneration(ApplicationEntity application, CompanyEntity company, HubEntity hub) {
// Validate application, company, and hub
Long applicationId = application.getId();
log.info("Starting NDG generation process for applicationId: {}", applicationId);
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
NdganagEntity ndganagEntity = ndganagRepository.findByVatNumber(company.getVatNumber());
AppointmentLoginResponse ndgResponse=new AppointmentLoginResponse();
if (ndganagEntity != null || ndganagEntity.getNdg() != null) {
ndgResponse.setNdg(ndganagEntity.getNdg());
}else {
ndgResponse = retrieveNdgByVatNumber(company.getVatNumber(), authorizationToken, hub, application);
}
if (isNdgValid(ndgResponse.getNdg())) {
saveNdg(application, company, ndgResponse.getNdg());
log.info("NDG successfully generated for applicationId: {}", applicationId);
} else {
log.info("Polling for NDG for applicationId: {}", applicationId);
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) {
log.info("Starting singleshot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId());
ApplicationEntity oldApplication = Utils.getClonedEntityForData(application);
CompanyEntity oldCompanyEntity = Utils.getClonedEntityForData(company);
long startTime = System.currentTimeMillis();
long twoHoursMs = TimeUnit.HOURS.toMillis(2);
try {
// 1) If NDG was already populated (perhaps by another thread), skip polling.
if (application.getNdg() != null) {
log.info("NDG already present for applicationId {}. Exiting singleshot polling.", application.getId());
return;
}
// 2) Attempt to create Visura (this may immediately return a valid NDG)
AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub);
// 2a) If createVisura gave us a valid NDG, persist & exit
String fetchedNdg = visuraResponse.getNdg();
if (isNdgValid(fetchedNdg)) {
log.info("Valid NDG retrieved from createVisura(): {} | applicationId: {}", fetchedNdg, application.getId());
company.setNdg(fetchedNdg);
application.setNdg(fetchedNdg);
application.setNdgStatus(NdgStatusEnum.NDG_GENERATED.getValue());
application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
application.setIdVisura(visuraResponse.getIdVisura());
companyRepository.save(company);
applicationRepository.save(application);
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build());
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build());
ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId());
Map<String, String> placeholders = new HashMap<>();
placeholders.put("{{call_name}}", application.getCall().getName());
placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber()));
notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION);
notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION);
log.info("NDG saved successfully for applicationId: {}", application.getId());
return;
}
// 2b) If no immediate NDG, fetch the Visura list JSON and parse out NDG
String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub);
log.debug("Parsing NDG from VisuraList JSON for applicationId: {}", application.getId());
String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson);
if (isNdgValid(parsedNdg)) {
log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId());
company.setNdg(parsedNdg);
application.setNdg(parsedNdg);
application.setNdgStatus(GepafinConstant.NDG_GENERATED);
application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
application.setIdVisura(visuraResponse.getIdVisura());
companyRepository.save(company);
applicationRepository.save(application);
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build());
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build());
ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId());
Map<String, String> placeholders = new HashMap<>();
placeholders.put("{{call_name}}", application.getCall().getName());
placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber()));
notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION);
notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION);
log.info("NDG saved successfully for applicationId: {}", application.getId());
return;
}
// 3) Neither direct API nor parsing yielded a valid NDG. Check timeout.
if (System.currentTimeMillis() - startTime > twoHoursMs) {
log.warn("NDG polling timed out after 2 hours for applicationId: {}", application.getId());
application.setNdgStatus(GepafinConstant.NDG_FAILED);
applicationRepository.save(application);
return;
}
// 4) No NDG yet—just return. The scheduler will retry in 15 minutes.
log.info("No valid NDG yet for applicationId {}. Returning to scheduler—next attempt in 15 minutes.", application.getId());
} catch (Exception e) {
log.error("Exception during NDG polling for applicationId: {}", application.getId(), e);
// If timeout after exception, mark NDG_FAILED
if (System.currentTimeMillis() - startTime > twoHoursMs) {
log.warn("Example: exiting singleshot polling due to timeout after exception for applicationId: {}", application.getId());
application.setNdgStatus(GepafinConstant.NDG_FAILED);
applicationRepository.save(application);
} else {
log.info("Exception occurred but not timed out for applicationId {}. Returning to scheduler for next attempt in 15 minutes.", application.getId());
}
}
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 saveNdg(ApplicationEntity application, CompanyEntity company, String ndg) {
ApplicationEntity oldApplication = Utils.getClonedEntityForData(application);
CompanyEntity oldCompanyEntity = Utils.getClonedEntityForData(company);
application.setNdg(ndg);
application.setNdgStatus(GepafinConstant.NDG_GENERATED);
application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
company.setNdg(ndg);
companyRepository.save(company);
applicationRepository.save(application);
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build());
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build());
ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId());
// Map<String, String> placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION);
Map<String, String> 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<Object> 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
String responseJson = getNdgFromExternalService(vatNumber, authorizationToken);
// 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<Object> 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) {
AppointmentLoginResponse loginResponse = new AppointmentLoginResponse();
String ndg=extractNdg(jsonResponse);
if (ndg==null){ return null;}
else {
loginResponse.setNdg(ndg);
}
return loginResponse;
}
private String extractNdg(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.");
return null;
}
JsonNode firstDataEntry = dataArray.get(0);
AppointmentLoginResponse response = new AppointmentLoginResponse();
if (firstDataEntry.has(GepafinConstant.NDG_STRING)) {
String ndg=normalizeNullValue(firstDataEntry.get(GepafinConstant.NDG_STRING).asText());
return ndg;
}
} catch (Exception e) {
log.error("Failed to parse response: {}", e.getMessage(), e);
throw new RuntimeException("Failed to parse NDG response.", e);
}
return null;
}
public 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<Object> 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<Object> 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<Object> appointmentResponse) {
if (appointmentResponse.getBody() != null) {
log.info("Appointment API Response : {}", appointmentResponse.getBody());
try {
Map<String, Object> responseBody = (Map<String, Object>) appointmentResponse.getBody();
// 1. Try to get appointment ID from data.id
if (responseBody.containsKey(GepafinConstant.DATA_STRING)) {
Map<String, Object> data = (Map<String, Object>) 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<Map<String, Object>> errors = (List<Map<String, Object>>) responseBody.get(GepafinConstant.ERROR_STRING);
if (errors != null && !errors.isEmpty()) {
Map<String, Object> firstError = errors.get(0);
if (firstError.containsKey(GepafinConstant.CAUSE_STRING)) {
Map<String, Object> cause = (Map<String, Object>) 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<AppointmentCreationRequest.RichiestaCliente> 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<AppointmentCreationRequest.RichiestaCliente> 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) {
log.info("Starting sync document upload for documentId: {}", documentId);
// 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<Object> 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);
}
}
private void startAsyncNdgProcessing(Long applicationId) {
if (executorMap.containsKey(applicationId)) {
log.warn("Async processing already running for applicationId: {}", applicationId);
return;
}
ApplicationEntity application = applicationService.validateApplication(applicationId);
CompanyEntity company = companyService.validateCompany(application.getCompanyId());
ApplicationEntity oldApplication = Utils.getClonedEntityForData(application);
CompanyEntity oldCompanyEntity = Utils.getClonedEntityForData(company);
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r);
t.setName("AsyncNdgPolling-" + applicationId);
return t;
});
executorMap.put(applicationId, scheduler);
application.setNdgStatus(NdgStatusEnum.NDG_IN_PROGRESS.getValue());
applicationRepository.save(application);
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build());
long startTime = System.currentTimeMillis();
long twoHoursMs = TimeUnit.HOURS.toMillis(2);
long fifteenMin = 15;
HubEntity hub = hubRepository.findByHubId(application.getHubId());
// 1. Run full NDG generation logic once upfront
processNdgGeneration(application, company, hub);
AtomicReference<ScheduledFuture<?>> futureRef = new AtomicReference<>();
// 2. Now define the polling logic ONLY
Runnable pollingTask = () -> {
RequestContextHolder.setRequestAttributes(requestAttributes, true);
Utils.setHttpServletRequestForThread(request, HttpMethodEnum.POST.getValue(),
GepafinConstant.CREATE_NDG, (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID));
try {
// Stop if timeout
if (System.currentTimeMillis() - startTime > twoHoursMs) {
log.warn("Polling timed out for applicationId {}. Marking NDG_FAILED.", applicationId);
application.setNdgStatus(GepafinConstant.NDG_FAILED);
applicationRepository.save(application);
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build());
futureRef.get().cancel(false);
shutdownScheduler(applicationId);
return;
}
// If NDG already present or marked failed
if (isNdgValid(application.getNdg()) || GepafinConstant.NDG_FAILED.equals(application.getNdgStatus())) {
log.info("NDG already present or failed for applicationId {}. Stopping polling.", applicationId);
futureRef.get().cancel(false);
shutdownScheduler(applicationId);
return;
}
// Only Visura polling here:
String visuraListJson = getVisuraList(application.getIdVisura(), hub.getAppointmentAuthTokenId(), application, hub);
String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson);
if (isNdgValid(parsedNdg)) {
log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId());
company.setNdg(parsedNdg);
application.setNdg(parsedNdg);
application.setNdgStatus(GepafinConstant.NDG_GENERATED);
application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
// application.setIdVisura(visuraResponse.getIdVisura());
companyRepository.save(company);
applicationRepository.save(application);
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build());
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyEntity).newData(company).build());
ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId());
Map<String, String> placeholders = new HashMap<>();
placeholders.put("{{call_name}}", application.getCall().getName());
placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber()));
notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION);
notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION);
log.info("NDG saved successfully for applicationId: {}", application.getId());
return;
}
} catch (Exception ex) {
log.error("Error during NDG polling: {}", ex.getMessage(), ex);
}
};
ScheduledFuture<?> future = scheduler.scheduleWithFixedDelay(pollingTask, fifteenMin, fifteenMin, TimeUnit.MINUTES);
futureRef.set(future);
}
public NdgResponse getNdgByVatNumber(String vatNumber, UserEntity userEntity) {
HubEntity hub=userEntity.getHub();
NdganagEntity ndganagEntity = ndganagRepository.findByVatNumber(vatNumber);
NdgResponse ndgResponse=new NdgResponse();
String jsonResponse=null;
String authorizationToken = hub.getAppointmentAuthTokenId();
if (ndganagEntity == null || ndganagEntity.getNdg() == null) {
try {
log.info("Initiating NDG retrieval by VAT number | HubId: {}, VAT: {}", hub.getId(), vatNumber);
// Prepare the NDG request
jsonResponse=getNdgFromExternalService(vatNumber, authorizationToken);
checkAndSaveNdg(jsonResponse, ndgResponse);
// Parse and return the NDG response
} catch (FeignException.Forbidden forbiddenException) {
log.error("403 Forbidden during NDG retrieval | HubId: {}", hub.getId());
logForbiddenError();
// Regenerate the token and retry
String newAuthorizationToken = regenerateTokenAndSave(hub, null);
jsonResponse= getNdgFromExternalService(vatNumber,newAuthorizationToken);
if (checkAndSaveNdg(jsonResponse, ndgResponse)) return null;
} catch (Exception e) {
log.error("Error during NDG retrieval |, HubId: {}, Message: {}", hub.getId(), e.getMessage(), e);
throw new RuntimeException("NDG retrieval failed.", e);
}
}else {
ndgResponse.setNdg(ndganagEntity.getNdg());
}
return ndgResponse;
}
private boolean checkAndSaveNdg(String jsonResponse, NdgResponse ndgResponse) {
String ndg=extractNdg(jsonResponse);
if (ndg==null){
return true;
}
else {
ndgResponse.setNdg(ndg);
}
return false;
}
private String getNdgFromExternalService(String vatNumber, String authorizationToken) {
AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber);
// Call the API to retrieve NDG
ResponseEntity<Object> response = appointmentApiService.getNdgByVatNumber(ndgRequest, authorizationToken);
String responseJson = Utils.convertObjectToJson(response.getBody());
return responseJson;
}
}