Skip to content

Commit 9e449b1

Browse files
author
pkryshtop
committed
feat: set requestId if not provided, log requestId on low level exceptions
1 parent badff41 commit 9e449b1

14 files changed

+728
-50
lines changed

smartling-api-commons/src/main/java/com/smartling/api/v2/client/ClientFactory.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import com.smartling.api.v2.client.exception.DefaultRestApiExceptionMapper;
88
import com.smartling.api.v2.client.exception.RestApiExceptionHandler;
99
import com.smartling.api.v2.client.exception.RestApiExceptionMapper;
10+
import com.smartling.api.v2.client.request.RequestContextFilter;
11+
import com.smartling.api.v2.client.request.RequestContextInvocationHandler;
1012
import com.smartling.api.v2.client.unmarshal.DetailsDeserializer;
1113
import com.smartling.api.v2.client.unmarshal.RestApiContextResolver;
1214
import com.smartling.api.v2.client.unmarshal.RestApiResponseReaderInterceptor;
@@ -40,6 +42,7 @@
4042
import javax.ws.rs.client.ClientResponseFilter;
4143
import javax.ws.rs.ext.ContextResolver;
4244
import javax.ws.rs.ext.ReaderInterceptor;
45+
import java.lang.reflect.InvocationHandler;
4346
import java.lang.reflect.Proxy;
4447
import java.util.Collections;
4548
import java.util.HashMap;
@@ -252,11 +255,15 @@ <T> T build(
252255
for (final ClientResponseFilter filter : clientResponseFilters)
253256
client.register(filter);
254257

258+
client.register(new DefaultRequestIdFilter());
259+
client.register(new RequestContextFilter());
260+
255261
final T proxy = client.proxy(klass);
256262
final RestApiExceptionHandler exceptionHandler = new RestApiExceptionHandler(exceptionMapper != null ? exceptionMapper : new DefaultRestApiExceptionMapper());
257-
final ExceptionDecoratorInvocationHandler<T> handler = new ExceptionDecoratorInvocationHandler<>(proxy, exceptionHandler);
258-
final CloseClientInvocationHandler closeClientInvocationHandler = new CloseClientInvocationHandler(handler, client.getResteasyClient());
263+
InvocationHandler handler = new ExceptionDecoratorInvocationHandler<>(proxy, exceptionHandler);
264+
handler = new CloseClientInvocationHandler(handler, client.getResteasyClient());
265+
handler = new RequestContextInvocationHandler(handler);
259266

260-
return (T) Proxy.newProxyInstance(klass.getClassLoader(), new Class[] { klass }, closeClientInvocationHandler);
267+
return (T) Proxy.newProxyInstance(klass.getClassLoader(), new Class[] { klass }, handler);
261268
}
262269
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.smartling.api.v2.client;
2+
3+
import org.apache.commons.lang3.StringUtils;
4+
5+
import javax.ws.rs.client.ClientRequestContext;
6+
import javax.ws.rs.client.ClientRequestFilter;
7+
import java.io.IOException;
8+
import java.util.UUID;
9+
10+
public class DefaultRequestIdFilter implements ClientRequestFilter
11+
{
12+
private static final String REQUEST_ID_HEADER = "X-SL-RequestId";
13+
14+
@Override
15+
public void filter(ClientRequestContext requestContext) throws IOException
16+
{
17+
String requestId = requestContext.getHeaderString(REQUEST_ID_HEADER);
18+
if (StringUtils.isBlank(requestId))
19+
{
20+
requestContext.getHeaders().addFirst(REQUEST_ID_HEADER, UUID.randomUUID().toString());
21+
}
22+
}
23+
}

smartling-api-commons/src/main/java/com/smartling/api/v2/client/exception/RestApiExceptionHandler.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.smartling.api.v2.client.exception;
22

3+
import com.smartling.api.v2.client.request.RequestContextHolder;
34
import com.smartling.api.v2.response.ErrorResponse;
45
import lombok.extern.slf4j.Slf4j;
56

67
import javax.ws.rs.ProcessingException;
78
import javax.ws.rs.WebApplicationException;
9+
import javax.ws.rs.client.ResponseProcessingException;
810
import javax.ws.rs.core.Response;
911
import java.lang.reflect.InvocationTargetException;
1012

@@ -39,9 +41,13 @@ else if (throwable instanceof ProcessingException && throwable.getCause() instan
3941
{
4042
restApiRuntimeException = (RestApiRuntimeException)throwable.getCause();
4143
}
44+
else if (throwable instanceof ResponseProcessingException)
45+
{
46+
restApiRuntimeException = new RestApiRuntimeException(throwable, ((ResponseProcessingException) throwable).getResponse(), null);
47+
}
4248
else
4349
{
44-
restApiRuntimeException = new RestApiRuntimeException(throwable);
50+
restApiRuntimeException = new RestApiRuntimeException(throwable, RequestContextHolder.getContext());
4551
}
4652

4753
restApiRuntimeException.setErrorDetails(errorDetails);

smartling-api-commons/src/main/java/com/smartling/api/v2/client/exception/RestApiRuntimeException.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package com.smartling.api.v2.client.exception;
22

3+
import com.smartling.api.v2.client.request.RequestContext;
34
import com.smartling.api.v2.response.Error;
45
import com.smartling.api.v2.response.ErrorResponse;
56
import com.smartling.api.v2.response.ResponseCode;
67
import lombok.extern.slf4j.Slf4j;
78

8-
import java.util.Collections;
9-
import java.util.List;
109
import javax.ws.rs.WebApplicationException;
1110
import javax.ws.rs.core.Response;
11+
import java.util.Collections;
12+
import java.util.List;
1213

1314
@Slf4j
1415
public class RestApiRuntimeException extends WebApplicationException
@@ -25,6 +26,12 @@ public RestApiRuntimeException(final Throwable cause)
2526
this.errorResponse = null;
2627
}
2728

29+
public RestApiRuntimeException(final Throwable cause, RequestContext requestContext)
30+
{
31+
super(cause, buildErrorResponse(requestContext));
32+
this.errorResponse = null;
33+
}
34+
2835
public RestApiRuntimeException(final Throwable cause, final Response response, final ErrorResponse errorResponse)
2936
{
3037
super(cause, response);
@@ -37,11 +44,7 @@ public void setErrorDetails(String errorDetails) {
3744

3845
public int getStatus()
3946
{
40-
final Response response = getResponse();
41-
if (response == null)
42-
return 500;
43-
44-
return response.getStatus();
47+
return getResponse().getStatus();
4548
}
4649

4750
public ResponseCode getResponseCode()
@@ -64,7 +67,7 @@ public List<Error> getErrors()
6467
@Override
6568
public String getMessage()
6669
{
67-
final String requestId = getResponse().getHeaderString(REQUEST_ID_HEADER);
70+
String requestId = getResponse().getHeaderString(REQUEST_ID_HEADER);
6871

6972
final StringBuilder errorMessage = new StringBuilder();
7073

@@ -89,4 +92,17 @@ public String getMessage()
8992

9093
return errorMessage.toString();
9194
}
95+
96+
private static Response buildErrorResponse(RequestContext requestContext)
97+
{
98+
if (requestContext != null && requestContext.getHeaders() != null)
99+
{
100+
return Response
101+
.status(Response.Status.INTERNAL_SERVER_ERROR)
102+
.header(REQUEST_ID_HEADER, requestContext.getHeaders().getFirst(REQUEST_ID_HEADER))
103+
.build();
104+
}
105+
106+
return Response.serverError().build();
107+
}
92108
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.smartling.api.v2.client.request;
2+
3+
import lombok.Data;
4+
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
5+
6+
import javax.ws.rs.client.ClientRequestContext;
7+
import javax.ws.rs.core.MultivaluedMap;
8+
import java.net.URI;
9+
10+
@Data
11+
public class RequestContext
12+
{
13+
private final String method;
14+
private final URI uri;
15+
private final MultivaluedMap<String, Object> headers;
16+
17+
public static RequestContext fromClientRequestContext(ClientRequestContext context)
18+
{
19+
MultivaluedMap<String, Object> headers = new MultivaluedMapImpl<>();
20+
headers.putAll(context.getHeaders());
21+
22+
return new RequestContext(context.getMethod(), context.getUri(), headers);
23+
}
24+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.smartling.api.v2.client.request;
2+
3+
import javax.ws.rs.client.ClientRequestContext;
4+
import javax.ws.rs.client.ClientRequestFilter;
5+
import java.io.IOException;
6+
7+
public class RequestContextFilter implements ClientRequestFilter
8+
{
9+
@Override
10+
public void filter(ClientRequestContext clientRequestContext) throws IOException
11+
{
12+
RequestContextHolder.setContext(RequestContext.fromClientRequestContext(clientRequestContext));
13+
}
14+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.smartling.api.v2.client.request;
2+
3+
public class RequestContextHolder
4+
{
5+
private static final ThreadLocal<RequestContext> REQUEST_CONTEXT = new ThreadLocal<>();
6+
7+
public static void setContext(RequestContext requestContext)
8+
{
9+
REQUEST_CONTEXT.set(requestContext);
10+
}
11+
12+
public static RequestContext getContext()
13+
{
14+
return REQUEST_CONTEXT.get();
15+
}
16+
17+
public static void clearContext()
18+
{
19+
REQUEST_CONTEXT.remove();
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.smartling.api.v2.client.request;
2+
3+
import lombok.RequiredArgsConstructor;
4+
5+
import java.lang.reflect.InvocationHandler;
6+
import java.lang.reflect.Method;
7+
8+
@RequiredArgsConstructor
9+
public class RequestContextInvocationHandler implements InvocationHandler
10+
{
11+
private final InvocationHandler delegate;
12+
13+
@Override
14+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
15+
{
16+
try
17+
{
18+
return delegate.invoke(proxy, method, args);
19+
}
20+
finally
21+
{
22+
RequestContextHolder.clearContext();
23+
}
24+
}
25+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.smartling.api.v2.client;
2+
3+
import org.junit.Before;
4+
import org.junit.Test;
5+
import org.junit.runner.RunWith;
6+
import org.mockito.Mock;
7+
import org.mockito.junit.MockitoJUnitRunner;
8+
9+
import javax.ws.rs.client.ClientRequestContext;
10+
import javax.ws.rs.core.MultivaluedMap;
11+
12+
import static org.mockito.ArgumentMatchers.eq;
13+
import static org.mockito.Mockito.any;
14+
import static org.mockito.Mockito.verify;
15+
import static org.mockito.Mockito.verifyNoMoreInteractions;
16+
import static org.mockito.Mockito.when;
17+
18+
@RunWith(MockitoJUnitRunner.class)
19+
public class DefaultRequestIdFilterTest
20+
{
21+
private static final String REQUEST_ID_HEADER = "X-SL-RequestId";
22+
23+
@Mock
24+
private ClientRequestContext requestContext;
25+
26+
@Mock
27+
private MultivaluedMap<String, Object> headers;
28+
29+
private final DefaultRequestIdFilter filter = new DefaultRequestIdFilter();
30+
31+
@Before
32+
public void setUp()
33+
{
34+
when(requestContext.getHeaders()).thenReturn(headers);
35+
}
36+
37+
@Test
38+
public void shouldNotAddHeaderWhenRequestIdHeaderExists() throws Exception
39+
{
40+
// Given
41+
when(requestContext.getHeaderString(REQUEST_ID_HEADER)).thenReturn("existing-request-id");
42+
43+
// When
44+
filter.filter(requestContext);
45+
46+
// Then
47+
verifyNoMoreInteractions(headers);
48+
}
49+
50+
@Test
51+
public void shouldAddHeaderWhenRequestIdHeaderDoesNotExist() throws Exception
52+
{
53+
// Given
54+
when(requestContext.getHeaderString(REQUEST_ID_HEADER)).thenReturn(null);
55+
56+
// When
57+
filter.filter(requestContext);
58+
59+
// Then
60+
verify(headers).addFirst(eq(REQUEST_ID_HEADER), any());
61+
verifyNoMoreInteractions(headers);
62+
}
63+
64+
@Test
65+
public void shouldAddHeaderWhenRequestIdHeaderIsEmpty() throws Exception
66+
{
67+
// Given
68+
when(requestContext.getHeaderString(REQUEST_ID_HEADER)).thenReturn("");
69+
70+
// When
71+
filter.filter(requestContext);
72+
73+
// Then
74+
verify(headers).addFirst(eq(REQUEST_ID_HEADER), any());
75+
verifyNoMoreInteractions(headers);
76+
}
77+
78+
@Test
79+
public void shouldAddHeaderWhenRequestIdHeaderIsWhitespace() throws Exception
80+
{
81+
// Given
82+
when(requestContext.getHeaderString(REQUEST_ID_HEADER)).thenReturn(" ");
83+
84+
// When
85+
filter.filter(requestContext);
86+
87+
// Then
88+
verify(headers).addFirst(eq(REQUEST_ID_HEADER), any());
89+
verifyNoMoreInteractions(headers);
90+
}
91+
}

0 commit comments

Comments
 (0)