From bee2f14419fb5ae1e70e752eb143f376722400f7 Mon Sep 17 00:00:00 2001 From: Sehjot Singh Pannu Date: Tue, 13 Oct 2026 12:10:25 +0530 Subject: [PATCH] Adding locking for multiple order creation --- .../emrlite/EmrLiteRestTemplateConfig.java | 6 ++++ .../impl/DiagnosticOrderServiceImpl.java | 35 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/main/java/com/iemr/flw/integration/provider/emrlite/EmrLiteRestTemplateConfig.java b/src/main/java/com/iemr/flw/integration/provider/emrlite/EmrLiteRestTemplateConfig.java index 87079a18..53494b68 100644 --- a/src/main/java/com/iemr/flw/integration/provider/emrlite/EmrLiteRestTemplateConfig.java +++ b/src/main/java/com/iemr/flw/integration/provider/emrlite/EmrLiteRestTemplateConfig.java @@ -14,10 +14,16 @@ public class EmrLiteRestTemplateConfig { private static final int VENDOR_CALL_TIMEOUT_MS = 20000; + // Result responses carry document assets inline as base64, so reads get more headroom than connects. + private static final int VENDOR_READ_TIMEOUT_MS = 60000; + // Timeouts are required, not optional: order pushes run while holding the per-beneficiary+orderType + // order lock (see DiagnosticOrderServiceImpl), so a hung vendor call without them would hold that lock forever. @Bean public RestTemplate emrLiteRestTemplate(EmrLiteTokenManager emrLiteTokenManager) { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(VENDOR_CALL_TIMEOUT_MS); + factory.setReadTimeout(VENDOR_READ_TIMEOUT_MS); RestTemplate restTemplate = new RestTemplate(factory); restTemplate.getInterceptors().add(new EmrLiteAuthInterceptor(emrLiteTokenManager)); return restTemplate; diff --git a/src/main/java/com/iemr/flw/service/impl/DiagnosticOrderServiceImpl.java b/src/main/java/com/iemr/flw/service/impl/DiagnosticOrderServiceImpl.java index d8dfc6ab..8c93e055 100644 --- a/src/main/java/com/iemr/flw/service/impl/DiagnosticOrderServiceImpl.java +++ b/src/main/java/com/iemr/flw/service/impl/DiagnosticOrderServiceImpl.java @@ -21,6 +21,9 @@ import com.iemr.flw.service.CampConfigService; import com.iemr.flw.service.DiagnosticDocumentService; import com.iemr.flw.service.DiagnosticOrderService; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -31,6 +34,10 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; @Service public class DiagnosticOrderServiceImpl implements DiagnosticOrderService { @@ -62,8 +69,36 @@ public class DiagnosticOrderServiceImpl implements DiagnosticOrderService { @Autowired private CampConfigService campConfigService; + private record OrderLockKey(Long beneficiaryId, String orderType) {} + private final LoadingCache orderLocks = CacheBuilder.newBuilder() + .weakValues() + .build(CacheLoader.from(key -> new ReentrantLock())); + + private static final long LOCK_WAIT_SECONDS = 30; + + private T withOrderLock(Long beneficiaryId, String orderType, Callable action) throws Exception { + if (beneficiaryId == null || orderType == null) { + throw new IllegalArgumentException("beneficiaryId and orderType are required"); + } + Lock lock = orderLocks.getUnchecked(new OrderLockKey(beneficiaryId, orderType)); + if (!lock.tryLock(LOCK_WAIT_SECONDS, TimeUnit.SECONDS)) { + throw new IllegalStateException("Another diagnostic order operation is still in progress for beneficiaryId=" + + beneficiaryId + ", orderType=" + orderType + " — please retry shortly"); + } + try { + return action.call(); + } finally { + lock.unlock(); + } + } + @Override public DiagnosticOrder createAndPushOrder(DiagnosticOrderRequestDto request) throws Exception { + DiagnosticOrderType orderType = DiagnosticOrderType.fromCode(request.getOrderType()); + return withOrderLock(request.getBeneficiaryId(), orderType.name(), () -> createAndPushOrderLocked(request)); + } + + private DiagnosticOrder createAndPushOrderLocked(DiagnosticOrderRequestDto request) throws Exception { Long beneficiaryId = request.getBeneficiaryId(); Long visitCode = request.getVisitCode(); DiagnosticOrderType orderType = DiagnosticOrderType.fromCode(request.getOrderType());