1308 lines
70 KiB
Java
1308 lines
70 KiB
Java
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.*;
|
||
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.ScheduledFuture;
|
||
import java.util.concurrent.TimeUnit;
|
||
import java.util.concurrent.atomic.AtomicReference;
|
||
import java.util.concurrent.ScheduledExecutorService;
|
||
|
||
@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<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);
|
||
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<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);
|
||
}
|
||
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) {
|
||
// If already polling for this applicationId, do nothing:
|
||
if (executorMap.containsKey(applicationId)) {
|
||
log.warn("Async processing already running for applicationId: {}", applicationId);
|
||
return;
|
||
}
|
||
|
||
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 = () -> {
|
||
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(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 {
|
||
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 single‐shot NDG polling attempt for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(), company.getId(), hub.getId());
|
||
|
||
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 single‐shot 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(GepafinConstant.NDG_GENERATED);
|
||
application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
|
||
application.setIdVisura(visuraResponse.getIdVisura());
|
||
|
||
companyRepository.save(company);
|
||
applicationRepository.save(application);
|
||
|
||
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);
|
||
|
||
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 single‐shot 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 saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) {
|
||
|
||
ApplicationEntity oldApplication = Utils.getClonedEntityForData(application);
|
||
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());
|
||
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
|
||
AppointmentNdgRequest ndgRequest = getAppointmentNdgRequest(vatNumber);
|
||
// Call the API to retrieve NDG
|
||
ResponseEntity<Object> 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<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) {
|
||
|
||
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<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) {
|
||
// 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);
|
||
}
|
||
}
|
||
} |