Skip to content

Commit 65f476f

Browse files
Merge pull request #359 from bcgov/feature/Grad2-3284
Gard2-3284 Set Request Headers
2 parents f38e224 + 8cd1640 commit 65f476f

File tree

9 files changed

+237
-25
lines changed

9 files changed

+237
-25
lines changed

.github/workflows/on.pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
with:
2626
distribution: 'corretto'
2727
java-version: 18
28-
- uses: actions/cache@v1
28+
- uses: actions/cache@v4
2929
with:
3030
path: ~/.m2/repository
3131
key: ${{ runner.os }}-maven-5Jun-${{ hashFiles('**/pom.xml') }}

api/src/main/java/ca/bc/gov/educ/api/studentgraduation/config/GradStudentGraduationConfig.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
package ca.bc.gov.educ.api.studentgraduation.config;
22

3+
import ca.bc.gov.educ.api.studentgraduation.util.EducGradStudentGraduationApiConstants;
4+
import ca.bc.gov.educ.api.studentgraduation.util.LogHelper;
5+
import ca.bc.gov.educ.api.studentgraduation.util.ThreadLocalStateUtil;
36
import org.modelmapper.ModelMapper;
47
import org.springframework.boot.web.client.RestTemplateBuilder;
58
import org.springframework.context.annotation.Bean;
69
import org.springframework.context.annotation.Configuration;
710
import org.springframework.web.client.RestTemplate;
11+
import org.springframework.web.reactive.function.client.ClientRequest;
12+
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
813
import org.springframework.web.reactive.function.client.WebClient;
14+
import reactor.core.publisher.Mono;
915
import reactor.netty.http.client.HttpClient;
1016

1117
@Configuration
1218
public class GradStudentGraduationConfig {
1319

20+
EducGradStudentGraduationApiConstants constants;
21+
LogHelper logHelper;
22+
1423
@Bean
1524
public ModelMapper modelMapper() {
1625

@@ -24,13 +33,39 @@ public ModelMapper modelMapper() {
2433
public WebClient webClient() {
2534
HttpClient client = HttpClient.create();
2635
client.warmup().block();
27-
return WebClient.builder().build();
36+
return WebClient.builder()
37+
.filter(setRequestHeaders())
38+
.filter(this.log())
39+
.build();
2840
}
2941

3042
@Bean
3143
public RestTemplate restTemplate(RestTemplateBuilder builder) {
3244
return builder.build();
3345
}
3446

47+
private ExchangeFilterFunction setRequestHeaders() {
48+
return (clientRequest, next) -> {
49+
ClientRequest modifiedRequest = ClientRequest.from(clientRequest)
50+
.header(EducGradStudentGraduationApiConstants.CORRELATION_ID, ThreadLocalStateUtil.getCorrelationID())
51+
.header(EducGradStudentGraduationApiConstants.USER_NAME, ThreadLocalStateUtil.getCurrentUser())
52+
.header(EducGradStudentGraduationApiConstants.REQUEST_SOURCE, EducGradStudentGraduationApiConstants.API_NAME)
53+
.build();
54+
return next.exchange(modifiedRequest);
55+
};
56+
}
57+
58+
private ExchangeFilterFunction log() {
59+
return (clientRequest, next) -> next
60+
.exchange(clientRequest)
61+
.doOnNext((clientResponse -> logHelper.logClientHttpReqResponseDetails(
62+
clientRequest.method(),
63+
clientRequest.url().toString(),
64+
clientResponse.statusCode().value(),
65+
clientRequest.headers().get(EducGradStudentGraduationApiConstants.CORRELATION_ID),
66+
clientRequest.headers().get(EducGradStudentGraduationApiConstants.REQUEST_SOURCE),
67+
constants.isSplunkLogHelperEnabled())
68+
));
69+
}
3570

3671
}

api/src/main/java/ca/bc/gov/educ/api/studentgraduation/config/RequestInterceptor.java

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
//import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
1818

1919
import java.time.Instant;
20+
import java.util.UUID;
2021

2122
@Component
2223
public class RequestInterceptor implements AsyncHandlerInterceptor {
@@ -37,21 +38,30 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
3738
validation.clear();
3839
// correlationID
3940
val correlationID = request.getHeader(EducGradStudentGraduationApiConstants.CORRELATION_ID);
40-
if (correlationID != null) {
41-
ThreadLocalStateUtil.setCorrelationID(correlationID);
41+
ThreadLocalStateUtil.setCorrelationID(correlationID != null ? correlationID : UUID.randomUUID().toString());
42+
43+
//Request Source
44+
val requestSource = request.getHeader(EducGradStudentGraduationApiConstants.REQUEST_SOURCE);
45+
if(requestSource != null) {
46+
ThreadLocalStateUtil.setRequestSource(requestSource);
4247
}
4348

44-
// username
45-
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
46-
if (auth instanceof JwtAuthenticationToken) {
47-
JwtAuthenticationToken authenticationToken = (JwtAuthenticationToken) auth;
48-
Jwt jwt = (Jwt) authenticationToken.getCredentials();
49-
String username = JwtUtil.getName(jwt);
50-
if (username != null) {
51-
ThreadLocalStateUtil.setCurrentUser(username);
49+
// Header userName
50+
val userName = request.getHeader(EducGradStudentGraduationApiConstants.USER_NAME);
51+
if (userName != null) {
52+
ThreadLocalStateUtil.setCurrentUser(userName);
53+
}
54+
else {
55+
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
56+
if (auth instanceof JwtAuthenticationToken) {
57+
JwtAuthenticationToken authenticationToken = (JwtAuthenticationToken) auth;
58+
Jwt jwt = (Jwt) authenticationToken.getCredentials();
59+
String username = JwtUtil.getName(jwt);
60+
if (username != null) {
61+
ThreadLocalStateUtil.setCurrentUser(username);
62+
}
5263
}
5364
}
54-
5565
return true;
5666
}
5767

@@ -66,10 +76,6 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
6676
@Override
6777
public void afterCompletion(@NonNull final HttpServletRequest request, final HttpServletResponse response, @NonNull final Object handler, final Exception ex) {
6878
LogHelper.logServerHttpReqResponseDetails(request, response, constants.isSplunkLogHelperEnabled());
69-
val correlationID = request.getHeader(EducGradStudentGraduationApiConstants.CORRELATION_ID);
70-
if (correlationID != null) {
71-
response.setHeader(EducGradStudentGraduationApiConstants.CORRELATION_ID, request.getHeader(EducGradStudentGraduationApiConstants.CORRELATION_ID));
72-
}
7379
// clear
7480
ThreadLocalStateUtil.clear();
7581
}

api/src/main/java/ca/bc/gov/educ/api/studentgraduation/service/AlgorithmRuleService.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,7 @@ public List<StudentGraduationAlgorithmData> getAllAlgorithmDataList(String acces
107107
List<StudentGraduationAlgorithmData> sList = new ArrayList<>();
108108
List<GraduationProgramCode> pList = webClient.get()
109109
.uri(constants.getProgramList())
110-
.headers(h -> {
111-
h.setBearerAuth(accessToken);
112-
h.set(EducGradStudentGraduationApiConstants.CORRELATION_ID, ThreadLocalStateUtil.getCorrelationID());
113-
})
110+
.headers(h -> h.setBearerAuth(accessToken))
114111
.retrieve()
115112
.bodyToMono(new ParameterizedTypeReference<List<GraduationProgramCode>>(){})
116113
.block();

api/src/main/java/ca/bc/gov/educ/api/studentgraduation/util/EducGradStudentGraduationApiConstants.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
public class EducGradStudentGraduationApiConstants {
1515

1616
public static final String CORRELATION_ID = "correlationID";
17-
17+
public static final String USER_NAME = "User-Name";
18+
public static final String REQUEST_SOURCE = "Request-Source";
19+
public static final String API_NAME = "EDUC-GRAD-STUDENT-GRADUATION-API";
1820
//API end-point Mapping constants
1921
public static final String API_ROOT_MAPPING = "";
2022
public static final String API_VERSION = "v1";
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package ca.bc.gov.educ.api.studentgraduation.util;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.stereotype.Component;
9+
10+
import java.io.IOException;
11+
import java.io.InputStream;
12+
13+
@Component
14+
public class JsonTransformer implements Transformer {
15+
16+
private static final Logger log = LoggerFactory.getLogger(JsonTransformer.class);
17+
private static final String MARSHALLING_MSG = "Time taken for unmarshalling response from String to {} is {} ms";
18+
19+
final ObjectMapper objectMapper;
20+
21+
@Autowired
22+
public JsonTransformer(ObjectMapper objectMapper) {
23+
this.objectMapper = objectMapper;
24+
}
25+
26+
@Override
27+
public Object unmarshall(byte[] input, Class<?> clazz) {
28+
Object result = null;
29+
long start = System.currentTimeMillis();
30+
try {
31+
result = objectMapper.readValue(input, clazz);
32+
} catch (IOException e) {
33+
log.error(e.getLocalizedMessage(), e);
34+
}
35+
log.debug("Time taken for unmarshalling response from bytes to {} is {} ms", clazz.getName(), (System.currentTimeMillis() - start));
36+
return result;
37+
}
38+
39+
40+
@Override
41+
public Object unmarshall(String input, Class<?> clazz) {
42+
Object result = null;
43+
long start = System.currentTimeMillis();
44+
try {
45+
result = objectMapper.readValue(input, clazz);
46+
} catch (IOException e) {
47+
log.error(e.getLocalizedMessage(), e);
48+
}
49+
log.debug(MARSHALLING_MSG, clazz.getName(), (System.currentTimeMillis() - start));
50+
return result;
51+
}
52+
53+
@Override
54+
public Object unmarshall(String input, TypeReference<?> valueTypeRef) {
55+
Object result = null;
56+
long start = System.currentTimeMillis();
57+
try {
58+
result = objectMapper.readValue(input, valueTypeRef);
59+
} catch (IOException e) {
60+
log.error(e.getLocalizedMessage(), e);
61+
}
62+
log.debug(MARSHALLING_MSG, valueTypeRef.getType().getTypeName(), (System.currentTimeMillis() - start));
63+
return result;
64+
}
65+
66+
@Override
67+
public Object unmarshall(InputStream input, Class<?> clazz) {
68+
Object result = null;
69+
long start = System.currentTimeMillis();
70+
try {
71+
result = objectMapper.readValue(input, clazz);
72+
} catch (IOException e) {
73+
log.error(e.getLocalizedMessage(), e);
74+
}
75+
log.debug("Time taken for unmarshalling response from stream to {} is {} ms", clazz.getName(), (System.currentTimeMillis() - start));
76+
return result;
77+
}
78+
79+
@Override
80+
public String marshall(Object input) {
81+
String result = null;
82+
try {
83+
result = objectMapper.writeValueAsString(input);
84+
} catch (IOException e) {
85+
log.error(e.getLocalizedMessage(), e);
86+
}
87+
return result;
88+
}
89+
90+
@Override
91+
public String getAccept() {
92+
return "application/json";
93+
}
94+
95+
@Override
96+
public String getContentType() {
97+
return "application/json";
98+
}
99+
100+
public <T> T convertValue(Object fromValue, TypeReference<T> toValueTypeRef) throws IllegalArgumentException {
101+
return objectMapper.convertValue(fromValue, toValueTypeRef);
102+
}
103+
}

api/src/main/java/ca/bc/gov/educ/api/studentgraduation/util/LogHelper.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@
44
import lombok.extern.slf4j.Slf4j;
55
import lombok.val;
66
import org.slf4j.MDC;
7+
import org.springframework.http.HttpMethod;
78
import org.springframework.lang.NonNull;
89

910
import jakarta.servlet.http.HttpServletRequest;
1011
import jakarta.servlet.http.HttpServletResponse;
1112
import java.time.Instant;
1213
import java.util.HashMap;
14+
import java.util.List;
1315
import java.util.Map;
1416

1517
@Slf4j
1618
public final class LogHelper {
1719
private static final ObjectMapper mapper = new ObjectMapper();
1820
private static final String EXCEPTION = "Exception ";
19-
21+
JsonTransformer jsonTransformer;
2022
private LogHelper() {
2123

2224
}
@@ -34,6 +36,10 @@ public static void logServerHttpReqResponseDetails(@NonNull final HttpServletReq
3436
if (correlationID != null) {
3537
httpMap.put("correlation_id", correlationID);
3638
}
39+
val requestSource = request.getHeader(EducGradStudentGraduationApiConstants.REQUEST_SOURCE);
40+
if (requestSource != null) {
41+
httpMap.put("request_source", requestSource);
42+
}
3743
httpMap.put("server_http_request_url", String.valueOf(request.getRequestURL()));
3844
httpMap.put("server_http_request_processing_time_ms", totalTime);
3945
httpMap.put("server_http_request_payload", String.valueOf(request.getAttribute("payload")));
@@ -45,4 +51,26 @@ public static void logServerHttpReqResponseDetails(@NonNull final HttpServletReq
4551
log.error(EXCEPTION, exception);
4652
}
4753
}
54+
55+
public void logClientHttpReqResponseDetails(@NonNull final HttpMethod method, final String url, final int responseCode, final List<String> correlationID,
56+
final List<String> requestSource, final boolean logging) {
57+
if (!logging) return;
58+
try {
59+
final Map<String, Object> httpMap = new HashMap<>();
60+
httpMap.put("client_http_response_code", responseCode);
61+
httpMap.put("client_http_request_method", method.toString());
62+
httpMap.put("client_http_request_url", url);
63+
if (correlationID != null) {
64+
httpMap.put("correlation_id", String.join(",", correlationID));
65+
}
66+
if (requestSource != null) {
67+
httpMap.put("request_source", String.join(",", requestSource));
68+
}
69+
MDC.putCloseable("httpEvent", jsonTransformer.marshall(httpMap));
70+
log.info("");
71+
MDC.clear();
72+
} catch (final Exception exception) {
73+
log.error(EXCEPTION, exception);
74+
}
75+
}
4876
}

api/src/main/java/ca/bc/gov/educ/api/studentgraduation/util/ThreadLocalStateUtil.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package ca.bc.gov.educ.api.studentgraduation.util;
22

33
public class ThreadLocalStateUtil {
4-
private static ThreadLocal<String> transaction = new ThreadLocal<>();
5-
private static ThreadLocal<String> user = new ThreadLocal<>();
4+
private static InheritableThreadLocal<String> transaction = new InheritableThreadLocal<>();
5+
private static InheritableThreadLocal<String> user = new InheritableThreadLocal<>();
6+
private static InheritableThreadLocal<String> requestSource = new InheritableThreadLocal();
67

78
/**
89
* Set the current correlationID for this thread
@@ -40,8 +41,25 @@ public static String getCurrentUser() {
4041
return user.get();
4142
}
4243

44+
/**
45+
* Get the request source for this thread
46+
*
47+
* @return the request source
48+
*/
49+
public static String getRequestSource() {
50+
return requestSource.get();
51+
}
52+
53+
/**
54+
* Set the request source for this thread
55+
*/
56+
public static void setRequestSource(String reqSource) {
57+
requestSource.set(reqSource);
58+
}
59+
4360
public static void clear() {
4461
transaction.remove();
4562
user.remove();
63+
requestSource.remove();
4664
}
4765
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package ca.bc.gov.educ.api.studentgraduation.util;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
5+
import java.io.InputStream;
6+
7+
public interface Transformer {
8+
9+
public Object unmarshall(byte[] input, Class<?> clazz);
10+
11+
public Object unmarshall(String input, Class<?> clazz);
12+
13+
public Object unmarshall(InputStream input, Class<?> clazz);
14+
15+
public Object unmarshall(String input, TypeReference<?> valueTypeRef);
16+
17+
public String marshall(Object input);
18+
19+
public String getAccept();
20+
21+
public String getContentType();
22+
23+
}

0 commit comments

Comments
 (0)