1 package org.imageconverter.infra;
2
3 import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
4 import static org.apache.commons.lang3.exception.ExceptionUtils.getMessage;
5 import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCause;
6 import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage;
7 import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace;
8 import static org.apache.commons.text.StringEscapeUtils.escapeJava;
9
10 import java.time.LocalDateTime;
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.HashMap;
14 import java.util.LinkedHashMap;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Objects;
18
19 import org.apache.commons.collections4.CollectionUtils;
20 import org.imageconverter.infra.exception.BaseApplicationException;
21 import org.slf4j.MDC;
22 import org.springframework.context.MessageSource;
23 import org.springframework.http.HttpHeaders;
24 import org.springframework.http.HttpStatus;
25 import org.springframework.http.ResponseEntity;
26 import org.springframework.http.converter.HttpMessageNotReadableException;
27 import org.springframework.web.bind.MethodArgumentNotValidException;
28 import org.springframework.web.bind.MissingServletRequestParameterException;
29 import org.springframework.web.context.request.WebRequest;
30 import org.springframework.web.servlet.NoHandlerFoundException;
31 import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
32
33
34
35
36
37
38 abstract class AbstractRestExceptionHandler extends ResponseEntityExceptionHandler {
39
40 protected final MessageSource messageSource;
41
42
43
44
45
46
47 protected AbstractRestExceptionHandler(final MessageSource messageSource) {
48 super();
49 this.messageSource = messageSource;
50 }
51
52
53
54
55 @Override
56 protected ResponseEntity<Object> handleMissingServletRequestParameter(final MissingServletRequestParameterException ex, final HttpHeaders headers, final HttpStatus status,
57 final WebRequest request) {
58
59 final var locale = request.getLocale();
60 final Object[] params = { ex.getParameterName() };
61
62 final var msg = messageSource.getMessage("exception.missingServletRequestParameter", params, locale);
63
64 return handleObjectException(msg, List.of(), ex, request, HttpStatus.BAD_REQUEST);
65 }
66
67
68
69
70 @Override
71 protected ResponseEntity<Object> handleHttpMessageNotReadable(final HttpMessageNotReadableException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
72
73 return handleObjectException(ex, request, HttpStatus.BAD_REQUEST);
74 }
75
76
77
78
79 @Override
80 protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
81
82 final var subErrors = new ArrayList<Map<String, String>>();
83
84 final var locale = request.getLocale();
85
86 ex.getBindingResult().getFieldErrors().forEach((error) -> {
87
88 final var errors = new HashMap<String, String>();
89
90 errors.put("object", error.getObjectName());
91 errors.put("field", error.getField());
92 errors.put("error", messageSource.getMessage(error, locale));
93
94 subErrors.add(errors);
95 });
96
97 final var msg = messageSource.getMessage("exception.handleMethodArgumentNotValid", null, locale);
98
99 return handleObjectException(msg, subErrors, ex, request, HttpStatus.BAD_REQUEST);
100 }
101
102
103
104
105 @Override
106 protected ResponseEntity<Object> handleNoHandlerFoundException(final NoHandlerFoundException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
107 final var locale = request.getLocale();
108
109 final var msg = messageSource.getMessage("exception.handleNoHandlerFound", null, locale);
110
111 return handleObjectException(msg, List.of(), ex, request, status);
112 }
113
114
115
116
117
118
119
120
121
122 protected ResponseEntity<Object> handleObjectException(final Throwable ex, final WebRequest request, final HttpStatus status) {
123
124 final var msg = ex instanceof BaseApplicationException ? escapeJava(getMessage(ex)) : escapeJava(getRootCauseMessage(ex));
125
126 final var body = buildResponseBody(msg, List.of(), status, ex, request);
127
128 if (logger.isErrorEnabled()) {
129 logger.error(msg, getRootCause(ex));
130 }
131
132 return new ResponseEntity<>(body, status);
133 }
134
135
136
137
138
139
140
141
142
143
144 protected ResponseEntity<Object> handleObjectException(final String msg, final Collection<Map<String, String>> subErrors, final Throwable ex, final WebRequest request, final HttpStatus status) {
145
146 final var body = buildResponseBody(msg, subErrors, status, ex, request);
147
148 if (logger.isErrorEnabled()) {
149 logger.error(escapeJava(getRootCauseMessage(ex)), getRootCause(ex));
150 }
151
152 return new ResponseEntity<>(body, status);
153 }
154
155 private Map<String, Object> buildResponseBody(final String message, final Collection<Map<String, String>> subErrors, final HttpStatus status, final Throwable ex, final WebRequest request) {
156 final var body = new LinkedHashMap<String, Object>();
157
158 body.put("timestamp", LocalDateTime.now().format(ISO_DATE_TIME));
159 body.put("status", status.value());
160 body.put("error", status.getReasonPhrase());
161 body.put("message", message);
162
163 if (CollectionUtils.isNotEmpty(subErrors)) {
164 body.put("subErrors", subErrors);
165 }
166
167 body.put("traceId", MDC.get("traceId"));
168 body.put("spanId", MDC.get("spanId"));
169
170 if (isTraceOn(request)) {
171 body.put("stackTrace", getStackTrace(ex));
172 }
173
174 return body;
175 }
176
177 private boolean isTraceOn(final WebRequest request) {
178
179 final var value = request.getParameterValues("trace");
180
181 return Objects.nonNull(value)
182 && value.length > 0
183 && "true".contentEquals(value[0]);
184 }
185
186 }