Subversion Repositories SmartDukaan

Rev

Rev 36517 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
33764 ranu 1
package com.spice.profitmandi.web.interceptor;
2
 
36510 amit 3
import com.spice.profitmandi.web.filter.RequestCachingFilter;
4
import org.apache.logging.log4j.LogManager;
33764 ranu 5
import org.apache.logging.log4j.Logger;
6
import org.springframework.beans.factory.annotation.Autowired;
36510 amit 7
import org.springframework.data.redis.core.RedisTemplate;
33764 ranu 8
import org.springframework.stereotype.Component;
9
import org.springframework.web.servlet.HandlerInterceptor;
10
import org.springframework.web.servlet.ModelAndView;
11
 
36510 amit 12
import javax.servlet.http.HttpServletRequest;
13
import javax.servlet.http.HttpServletResponse;
14
import java.security.MessageDigest;
15
import java.util.concurrent.TimeUnit;
33764 ranu 16
 
17
@Component
18
public class PostInterceptor implements HandlerInterceptor {
19
 
20
    private static final Logger LOGGER = LogManager.getLogger(PostInterceptor.class);
36510 amit 21
    private static final String IDEM_PREFIX = "idem:";
22
    private static final long IDEM_TTL_SECONDS = 300;
23
    private static final String REQUEST_ATTR_IDEM_KEY = "postInterceptor.idemKey";
33764 ranu 24
 
25
    @Autowired
36510 amit 26
    private RedisTemplate<String, Object> redisTemplate;
33764 ranu 27
 
36510 amit 28
    @Override
29
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
36517 amit 30
        if (!"POST".equalsIgnoreCase(request.getMethod())) {
36510 amit 31
            return true;
32
        }
35272 amit 33
 
36517 amit 34
        String idempotencyHeader = request.getHeader("IdempotencyKey");
36510 amit 35
        String idemKey = buildIdempotencyKey(request, idempotencyHeader);
36
        if (idemKey == null) {
37
            return true;
38
        }
39
 
40
        String redisKey = IDEM_PREFIX + idemKey;
41
        Boolean claimed = redisTemplate.opsForValue()
42
                .setIfAbsent(redisKey, "pending", IDEM_TTL_SECONDS, TimeUnit.SECONDS);
43
 
44
        if (Boolean.TRUE.equals(claimed)) {
45
            request.setAttribute(REQUEST_ATTR_IDEM_KEY, idemKey);
46
            return true;
47
        }
48
 
49
        LOGGER.info("Duplicate request detected: idemKey={}, uri={}", idemKey, request.getRequestURI());
50
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
51
        response.getWriter().write("Duplicate request.");
52
        return false;
53
    }
54
 
33764 ranu 55
    @Override
36510 amit 56
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
33764 ranu 57
            throws Exception {
58
    }
59
 
60
    @Override
36510 amit 61
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
33764 ranu 62
            throws Exception {
36510 amit 63
        String idemKey = (String) request.getAttribute(REQUEST_ATTR_IDEM_KEY);
64
        if (idemKey == null) {
65
            return;
66
        }
67
        String redisKey = IDEM_PREFIX + idemKey;
36988 vikas 68
        // On error, release the claim immediately so the caller can retry without waiting.
69
        // On success, leave the key untouched: keep its original claim-time TTL (set in
70
        // preHandle) and do NOT re-set it. Re-setting refreshed the TTL on every completion,
71
        // sliding the dedupe window forward and falsely rejecting later legitimate submissions.
72
        if (ex != null || response.getStatus() < 200 || response.getStatus() >= 300) {
36510 amit 73
            redisTemplate.delete(redisKey);
74
        }
33764 ranu 75
    }
76
 
36510 amit 77
    private String buildIdempotencyKey(HttpServletRequest request, String header) {
78
        if (header != null && !header.isEmpty()) {
79
            String uri = request.getRequestURI();
80
            String query = request.getQueryString();
81
            String scope = header + "|" + uri + (query != null ? "?" + query : "");
82
            return sha256(scope.getBytes(java.nio.charset.StandardCharsets.UTF_8));
83
        }
84
        if ("POST".equalsIgnoreCase(request.getMethod())
85
                && request instanceof RequestCachingFilter.CachedBodyRequest) {
86
            byte[] body = ((RequestCachingFilter.CachedBodyRequest) request).getCachedBody();
87
            if (body.length > 0) {
88
                return sha256(body);
33764 ranu 89
            }
36510 amit 90
        }
91
        return null;
92
    }
33764 ranu 93
 
36510 amit 94
    private static String sha256(byte[] data) {
95
        try {
96
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
97
            byte[] hash = digest.digest(data);
98
            StringBuilder hex = new StringBuilder(64);
99
            for (byte b : hash) {
100
                hex.append(String.format("%02x", b));
34824 vikas 101
            }
36510 amit 102
            return hex.toString();
103
        } catch (Exception e) {
104
            throw new RuntimeException("SHA-256 not available", e);
33764 ranu 105
        }
106
    }
107
}