Done ticket GEPAFINBE-228

This commit is contained in:
rajesh
2025-06-05 19:40:31 +05:30
parent 4482fb003f
commit 3b015252e2
4 changed files with 203 additions and 105 deletions

View File

@@ -571,7 +571,7 @@ public class GepafinConstant {
public static final String PROTOCOL_EXTERNAL_NUMBER="NUM_PG"; public static final String PROTOCOL_EXTERNAL_NUMBER="NUM_PG";
public static final String PROTOCOL_EXTERNAL_DATE="DATA_PG_DT"; public static final String PROTOCOL_EXTERNAL_DATE="DATA_PG_DT";
public static final String PROTOCOL_DOC_SUFFIX="#noauth"; public static final String PROTOCOL_DOC_SUFFIX="#noauth";
public static final String CREATE_NDG="CHECK_OR_CREATE_NDG_CODE";
} }

View File

@@ -18,10 +18,7 @@ import net.gepafin.tendermanagement.entities.ApplicationEvaluationEntity;
import net.gepafin.tendermanagement.entities.CompanyEntity; import net.gepafin.tendermanagement.entities.CompanyEntity;
import net.gepafin.tendermanagement.entities.DocumentEntity; import net.gepafin.tendermanagement.entities.DocumentEntity;
import net.gepafin.tendermanagement.entities.HubEntity; import net.gepafin.tendermanagement.entities.HubEntity;
import net.gepafin.tendermanagement.enums.ApplicationStatusTypeEnum; import net.gepafin.tendermanagement.enums.*;
import net.gepafin.tendermanagement.enums.DocumentSourceTypeEnum;
import net.gepafin.tendermanagement.enums.NotificationTypeEnum;
import net.gepafin.tendermanagement.enums.VersionActionTypeEnum;
import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest; import net.gepafin.tendermanagement.model.request.AppointmentCreationRequest;
import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest; import net.gepafin.tendermanagement.model.request.AppointmentNdgRequest;
import net.gepafin.tendermanagement.model.request.AppointmentVisuraListRequest; import net.gepafin.tendermanagement.model.request.AppointmentVisuraListRequest;
@@ -66,7 +63,10 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.ScheduledExecutorService;
@Slf4j @Slf4j
@Component @Component
@@ -150,8 +150,9 @@ public class AppointmentDao {
@Autowired @Autowired
private ApplicationEvaluationDao applicationEvaluationDao; private ApplicationEvaluationDao applicationEvaluationDao;
private final Map<Long, ExecutorService> executorMap = new ConcurrentHashMap<>(); private final Map<Long, ScheduledExecutorService> executorMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, ExecutorService> threadForDocumentMap = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Long, ExecutorService> threadForDocumentMap = new ConcurrentHashMap<>();
private static final ThreadLocal<Long> threadLocalHubId = new ThreadLocal<>(); private static final ThreadLocal<Long> threadLocalHubId = new ThreadLocal<>();
@@ -432,35 +433,89 @@ public class AppointmentDao {
} }
private void startAsyncNdgProcessing(Long applicationId) { private void startAsyncNdgProcessing(Long applicationId) {
// Check if a thread is already running for this application // If already polling for this applicationId, do nothing:
if (executorMap.containsKey(applicationId)) { if (executorMap.containsKey(applicationId)) {
log.warn("Async processing already running for applicationId: {}", applicationId); log.warn("Async processing already running for applicationId: {}", applicationId);
return; return;
} }
// Create a dedicated thread for asynchronous processing ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> {
ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { Thread t = new Thread(runnable);
Thread thread = new Thread(runnable); t.setName("AsyncNdgProcessing-" + applicationId);
thread.setName("AsyncNdgProcessing-" + applicationId); return t;
return thread;
}); });
executorMap.put(applicationId, executor); executorMap.put(applicationId, scheduler);
executor.submit(() -> { // 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 { try {
log.info("Starting async processing for applicationId: {}", applicationId); // 1) If 2 hours have already passed, mark as FAILED and shut down:
processNdgGeneration(applicationId); if (System.currentTimeMillis() - startTime > twoHoursMs) {
} catch (Exception e) { ApplicationEntity app = applicationService.validateApplication(applicationId);
log.error("Error in async NDG processing for applicationId: {}", applicationId, e); log.warn("2-hour timeout reached for applicationId {}. Marking NDG_FAILED.", applicationId);
} finally { app.setNdgStatus(GepafinConstant.NDG_FAILED);
// Cleanup resources applicationRepository.save(app);
ExecutorService executorToShutdown = executorMap.remove(applicationId);
if (executorToShutdown != null) { futureRef.get().cancel(false);
executorToShutdown.shutdown(); 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);
} }
log.info("Async processing completed for applicationId: {}", 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) { private void processNdgGeneration(Long applicationId) {
@@ -489,97 +544,108 @@ public class AppointmentDao {
saveNdgAndIdVisura(application, company, ndgResponse.getNdg()); saveNdgAndIdVisura(application, company, ndgResponse.getNdg());
log.info("NDG successfully generated for applicationId: {}", applicationId); log.info("NDG successfully generated for applicationId: {}", applicationId);
} else { } else {
// If NDG isn't immediately available, start polling log.info("Polling for NDG for applicationId: {}", applicationId);
handleNdgPolling(application, company, hub, authorizationToken); handleNdgPolling(application, company, hub, authorizationToken);
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", log.error("Exception occurred during NDG generation. ApplicationId: {}, CompanyId: {}, HubId: {}, Error: {}", applicationId, company.getId(), hub.getId(),
applicationId, company.getId(), hub.getId(), e.getMessage(), e); e.getMessage(), e);
} }
} }
private void handleNdgPolling(ApplicationEntity application, CompanyEntity company, HubEntity hub, String authorizationToken) { 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());
long startTime = System.currentTimeMillis();
long twoHoursMs = TimeUnit.HOURS.toMillis(2);
try { try {
log.info("Starting NDG polling for applicationId: {}, CompanyId: {}, HubId: {}", application.getId(),company.getId(), hub.getId()); // 1) If NDG was already populated (perhaps by another thread), skip polling.
long startTime = System.currentTimeMillis(); if (application.getNdg() != null) {
log.info("NDG already present for applicationId {}. Exiting singleshot polling.", application.getId());
while (true) { return;
if (application.getNdg() != null) { }
log.info("NDG retrieved for applicationId: {}", application.getId());
break; // 2) Attempt to create Visura (this may immediately return a valid NDG)
} AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub);
try { // 2a) If createVisura gave us a valid NDG, persist & exit
// Fetch Ndg via creating visura String fetchedNdg = visuraResponse.getNdg();
AppointmentLoginResponse visuraResponse = createVisura(company, authorizationToken, hub); if (isNdgValid(fetchedNdg)) {
if (isNdgValid(visuraResponse.getNdg())) { log.info("Valid NDG retrieved from createVisura(): {} | applicationId: {}", fetchedNdg, application.getId());
log.info("Valid NDG retrieved from create visura api response: {} | ApplicationId: {}", visuraResponse.getNdg(), application.getId());
company.setNdg(visuraResponse.getNdg()); company.setNdg(fetchedNdg);
application.setNdg(visuraResponse.getNdg()); application.setNdg(fetchedNdg);
application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setNdgStatus(GepafinConstant.NDG_GENERATED);
application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
application.setIdVisura(visuraResponse.getIdVisura()); application.setIdVisura(visuraResponse.getIdVisura());
applicationRepository.save(application);
companyRepository.save(company); companyRepository.save(company);
ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation( applicationRepository.save(application);
application.getApplicationEvaluationId());
Map<String, String> placeHolders = new HashMap<>(); ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId());
placeHolders.put("{{call_name}}", application.getCall().getName()); Map<String, String> placeholders = new HashMap<>();
placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); placeholders.put("{{call_name}}", application.getCall().getName());
notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber()));
notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION);
log.info("Got NDG and saved successfully for applicationId: {}", application.getId()); notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION);
break;
} else { log.info("NDG saved successfully for applicationId: {}", application.getId());
// Fetch Visura list and attempt to parse NDG return;
String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub); }
log.debug("Parsing NDG from visura list response | ApplicationId: {}", application.getId());
String ndg = parseNdgFromVisuraListResponse(visuraListJson); // 2b) If no immediate NDG, fetch the Visura list JSON and parse out NDG
if (isNdgValid(ndg)) { String visuraListJson = getVisuraList(visuraResponse.getIdVisura(), authorizationToken, application, hub);
// CompanyEntity oldCompanyData = Utils.getClonedEntityForData(company); log.debug("Parsing NDG from VisuraList JSON for applicationId: {}", application.getId());
// ApplicationEntity oldApplicationData = Utils.getClonedEntityForData(application); String parsedNdg = parseNdgFromVisuraListResponse(visuraListJson);
log.info("Valid NDG retrieved: {} | ApplicationId: {}", ndg, application.getId()); if (isNdgValid(parsedNdg)) {
company.setNdg(ndg); log.info("Valid NDG parsed from VisuraList: {} | applicationId: {}", parsedNdg, application.getId());
application.setNdg(ndg);
application.setNdgStatus(GepafinConstant.NDG_GENERATED); company.setNdg(parsedNdg);
application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); application.setNdg(parsedNdg);
application.setIdVisura(visuraResponse.getIdVisura()); application.setNdgStatus(GepafinConstant.NDG_GENERATED);
applicationRepository.save(application); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
companyRepository.save(company); application.setIdVisura(visuraResponse.getIdVisura());
ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(
application.getApplicationEvaluationId()); companyRepository.save(company);
Map<String, String> placeHolders = new HashMap<>(); applicationRepository.save(application);
placeHolders.put("{{call_name}}", application.getCall().getName());
placeHolders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber())); ApplicationEvaluationEntity eval = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId());
notificationDao.sendNotificationToInstructor(placeHolders, applicationEvaluationEntity, NotificationTypeEnum.NDG_GENERATION); Map<String, String> placeholders = new HashMap<>();
notificationDao.sendNotificationToSuperUser(application, placeHolders, NotificationTypeEnum.NDG_GENERATION); placeholders.put("{{call_name}}", application.getCall().getName());
log.info("NDG saved successfully for applicationId: {}", application.getId()); placeholders.put("{{protocol_number}}", String.valueOf(application.getProtocol().getProtocolNumber()));
break; notificationDao.sendNotificationToInstructor(placeholders, eval, NotificationTypeEnum.NDG_GENERATION);
} notificationDao.sendNotificationToSuperUser(application, placeholders, NotificationTypeEnum.NDG_GENERATION);
}
// Check if polling has timed out log.info("NDG saved successfully for applicationId: {}", application.getId());
if (System.currentTimeMillis() - startTime > TimeUnit.HOURS.toMillis(2)) { return;
log.warn("NDG polling timed out for applicationId: {}", application.getId()); }
application.setNdgStatus(GepafinConstant.NDG_FAILED);
applicationRepository.save(application); // 3) Neither direct API nor parsing yielded a valid NDG. Check timeout.
break; if (System.currentTimeMillis() - startTime > twoHoursMs) {
} log.warn("NDG polling timed out after 2 hours for applicationId: {}", application.getId());
application.setNdgStatus(GepafinConstant.NDG_FAILED);
// Wait before the next polling attempt applicationRepository.save(application);
Thread.sleep(TimeUnit.MINUTES.toMillis(15)); return;
} catch (InterruptedException e) { }
log.warn("NDG polling interrupted for applicationId: {}", application.getId());
Thread.currentThread().interrupt(); // 4) No NDG yet—just return. The scheduler will retry in 15 minutes.
break; log.info("No valid NDG yet for applicationId {}. Returning to scheduler—next attempt in 15 minutes.", application.getId());
} catch (Exception e) { } catch (Exception e) {
log.error("Error during NDG polling for applicationId: {}", application.getId(), 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());
} }
} finally {
log.info("NDG polling completed for applicationId: {}", application.getId());
} }
log.info("NDG polling completed for applicationId: {}", application.getId());
} }
private static String getBearerToken(HubEntity hub) { private static String getBearerToken(HubEntity hub) {
@@ -594,12 +660,15 @@ public class AppointmentDao {
private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) { private void saveNdgAndIdVisura(ApplicationEntity application, CompanyEntity company, String ndg) {
ApplicationEntity oldApplication = Utils.getClonedEntityForData(application);
application.setNdg(ndg); application.setNdg(ndg);
application.setNdgStatus(GepafinConstant.NDG_GENERATED); application.setNdgStatus(GepafinConstant.NDG_GENERATED);
application.setStatus(ApplicationStatusTypeEnum.NDG.getValue()); application.setStatus(ApplicationStatusTypeEnum.NDG.getValue());
company.setNdg(ndg); company.setNdg(ndg);
companyRepository.save(company); companyRepository.save(company);
applicationRepository.save(application); applicationRepository.save(application);
loggingUtil.addVersionHistory(
VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldApplication).newData(application).build());
ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId()); ApplicationEvaluationEntity applicationEvaluationEntity = applicationEvaluationService.validateApplicationEvaluation(application.getApplicationEvaluationId());
// Map<String, String> placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION); // Map<String, String> placeHolders = notificationDao.sendNotificationToBeneficiary(application, NotificationTypeEnum.NDG_GENERATION);
Map<String, String> placeHolders = new HashMap<>(); Map<String, String> placeHolders = new HashMap<>();

View File

@@ -0,0 +1,18 @@
package net.gepafin.tendermanagement.enums;
import com.fasterxml.jackson.annotation.JsonValue;
public enum HttpMethodEnum {
POST("POST"), PUT("PUT"), GET("GET"), DELETE("DELETE");
private String value;
HttpMethodEnum(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return value;
}
}

View File

@@ -1052,5 +1052,16 @@ public class Utils {
headers.add(org.apache.http.HttpHeaders.USER_AGENT, "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"); headers.add(org.apache.http.HttpHeaders.USER_AGENT, "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0");
return headers; return headers;
} }
public static void setHttpServletRequestForThread(String redirectURI, String methodType,HttpServletRequest request,String remoteUser) {
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
mockRequest.setRequestURI(redirectURI);
mockRequest.setMethod(methodType);
mockRequest.setRemoteUser(remoteUser);
Long userActionId = (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID);
mockRequest.setAttribute(GepafinConstant.USER_ACTION_ID,userActionId );
ServletRequestAttributes attributes = new ServletRequestAttributes(mockRequest);
RequestContextHolder.setRequestAttributes(attributes, true);
}
} }