11package io .mosip .kernel .keymanagerservice .config ;
22
3+ import jakarta .annotation .PostConstruct ;
34import jakarta .servlet .http .HttpServletRequest ;
45import jakarta .servlet .http .HttpServletRequestWrapper ;
56
1415import org .springframework .web .servlet .mvc .method .annotation .ResponseBodyAdvice ;
1516import org .springframework .web .util .ContentCachingRequestWrapper ;
1617
18+ import com .fasterxml .jackson .core .JsonParser ;
19+ import com .fasterxml .jackson .core .JsonToken ;
1720import com .fasterxml .jackson .databind .ObjectMapper ;
1821import com .fasterxml .jackson .datatype .jsr310 .JavaTimeModule ;
1922
20- import io .mosip .kernel .core .http .RequestWrapper ;
2123import io .mosip .kernel .core .http .ResponseFilter ;
2224import io .mosip .kernel .core .http .ResponseWrapper ;
2325import io .mosip .kernel .core .logger .spi .Logger ;
24- import io .mosip .kernel .core .util .EmptyCheckUtils ;
2526
2627/**
2728 * @author Bal Vikash Sharma
@@ -36,6 +37,14 @@ public class ResponseBodyAdviceConfig implements ResponseBodyAdvice<ResponseWrap
3637 @ Autowired
3738 private ObjectMapper objectMapper ;
3839
40+ @ PostConstruct
41+ public void init () {
42+ // Register JavaTimeModule once at startup on the shared singleton ObjectMapper.
43+ // Registering per-request (150 RPS) creates object churn and mutates shared
44+ // state concurrently — both corrected here.
45+ objectMapper .registerModule (new JavaTimeModule ());
46+ }
47+
3948 /*
4049 * (non-Javadoc)
4150 *
@@ -63,27 +72,21 @@ public ResponseWrapper<?> beforeBodyWrite(ResponseWrapper<?> body, MethodParamet
6372 MediaType selectedContentType , Class <? extends HttpMessageConverter <?>> selectedConverterType ,
6473 ServerHttpRequest request , ServerHttpResponse response ) {
6574
66- RequestWrapper <?> requestWrapper = null ;
67- String requestBody = null ;
68-
6975 try {
7076 HttpServletRequest httpServletRequest = ((ServletServerHttpRequest ) request ).getServletRequest ();
7177
78+ byte [] cachedBody = null ;
7279 if (httpServletRequest instanceof ContentCachingRequestWrapper ) {
73- requestBody = new String ((( ContentCachingRequestWrapper ) httpServletRequest ).getContentAsByteArray () );
80+ cachedBody = (( ContentCachingRequestWrapper ) httpServletRequest ).getContentAsByteArray ();
7481 } else if (httpServletRequest instanceof HttpServletRequestWrapper
7582 && ((HttpServletRequestWrapper ) httpServletRequest )
76- .getRequest () instanceof ContentCachingRequestWrapper ) {
77- requestBody = new String (
78- ((ContentCachingRequestWrapper ) ((HttpServletRequestWrapper ) httpServletRequest ).getRequest ())
79- .getContentAsByteArray ());
83+ .getRequest () instanceof ContentCachingRequestWrapper ) {
84+ cachedBody = ((ContentCachingRequestWrapper ) ((HttpServletRequestWrapper ) httpServletRequest ).getRequest ())
85+ .getContentAsByteArray ();
8086 }
8187
82- objectMapper .registerModule (new JavaTimeModule ());
83- if (!EmptyCheckUtils .isNullEmpty (requestBody )) {
84- requestWrapper = objectMapper .readValue (requestBody , RequestWrapper .class );
85- body .setId (requestWrapper .getId ());
86- body .setVersion (requestWrapper .getVersion ());
88+ if (cachedBody != null && cachedBody .length > 0 ) {
89+ extractAndSetIdVersion (body , cachedBody );
8790 }
8891 body .setErrors (null );
8992 return body ;
@@ -93,4 +96,42 @@ public ResponseWrapper<?> beforeBodyWrite(ResponseWrapper<?> body, MethodParamet
9396 return body ;
9497 }
9598
96- }
99+ /**
100+ * Extracts only the top-level "id" and "version" fields from the cached
101+ * (possibly truncated) request body using a streaming JSON parser.
102+ *
103+ * ReqResFilter caps the cached body at 4096 bytes. A full ObjectMapper.readValue()
104+ * fails with JsonEOFException when the encrypted "data" field spans the truncation
105+ * boundary (column 4097). The streaming parser reads token-by-token and stops as
106+ * soon as both fields are found — which happens within the first ~100 bytes since
107+ * "id" and "version" are always the first two fields in the MOSIP RequestWrapper
108+ * JSON envelope — long before any truncation can occur.
109+ */
110+ private void extractAndSetIdVersion (ResponseWrapper <?> body , byte [] cachedBody ) {
111+ try (JsonParser parser = objectMapper .getFactory ().createParser (cachedBody )) {
112+ String id = null ;
113+ String version = null ;
114+ JsonToken token ;
115+ while ((token = parser .nextToken ()) != null ) {
116+ if (id != null && version != null ) break ;
117+ if (token == JsonToken .FIELD_NAME ) {
118+ String fieldName = parser .getCurrentName ();
119+ token = parser .nextToken ();
120+ if ("id" .equals (fieldName ) && token == JsonToken .VALUE_STRING ) {
121+ id = parser .getText ();
122+ } else if ("version" .equals (fieldName ) && token == JsonToken .VALUE_STRING ) {
123+ version = parser .getText ();
124+ } else if (token == JsonToken .START_OBJECT || token == JsonToken .START_ARRAY ) {
125+ if (id != null && version != null ) break ;
126+ parser .skipChildren ();
127+ }
128+ }
129+ }
130+ if (id != null ) body .setId (id );
131+ if (version != null ) body .setVersion (version );
132+ } catch (Exception e ) {
133+ mosipLogger .debug ("" , "" , "" , "Could not extract id/version from cached request body: " + e .getMessage ());
134+ }
135+ }
136+
137+ }
0 commit comments