1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.exception;
18
19 import java.util.ArrayList;
20 import java.util.List;
21 import lombok.AllArgsConstructor;
22 import lombok.EqualsAndHashCode;
23 import lombok.Getter;
24 import lombok.NoArgsConstructor;
25 import lombok.Setter;
26 import lombok.ToString;
27 import org.springframework.boot.context.properties.ConfigurationProperties;
28 import org.springframework.http.HttpStatus;
29
30
31
32
33
34
35 @ConfigurationProperties(prefix = "bremersee.exception-mapping")
36 @Getter
37 @Setter
38 @ToString
39 @EqualsAndHashCode
40 public class RestApiExceptionMapperProperties {
41
42
43
44
45 private List<String> apiPaths = new ArrayList<>();
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 private ExceptionMapping defaultExceptionMapping;
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93 private List<ExceptionMapping> exceptionMappings = new ArrayList<>();
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141 private ExceptionMappingConfig defaultExceptionMappingConfig;
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162 private List<ExceptionMappingConfig> exceptionMappingConfigs = new ArrayList<>();
163
164
165
166
167 public RestApiExceptionMapperProperties() {
168
169 defaultExceptionMapping = new ExceptionMapping();
170 defaultExceptionMapping.setCode(null);
171 defaultExceptionMapping.setMessage(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
172 defaultExceptionMapping.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
173 defaultExceptionMapping.setExceptionClassName("*");
174
175 defaultExceptionMappingConfig = new ExceptionMappingConfig();
176
177 exceptionMappings.add(new ExceptionMapping(
178 IllegalArgumentException.class.getName(),
179 HttpStatus.BAD_REQUEST,
180 null));
181
182 exceptionMappings.add(new ExceptionMapping(
183 "org.springframework.security.access.AccessDeniedException",
184 HttpStatus.FORBIDDEN,
185 null));
186
187 exceptionMappings.add(new ExceptionMapping(
188 "javax.persistence.EntityNotFoundException",
189 HttpStatus.NOT_FOUND,
190 null));
191 }
192
193
194
195
196
197
198
199 @SuppressWarnings("WeakerAccess")
200 public ExceptionMapping findExceptionMapping(Throwable throwable) {
201 return getExceptionMappings()
202 .stream()
203 .filter(exceptionMapping -> matches(
204 throwable, exceptionMapping.getExceptionClassName()))
205 .findFirst()
206 .orElseGet(this::getDefaultExceptionMapping);
207 }
208
209
210
211
212
213
214
215 @SuppressWarnings("WeakerAccess")
216 public ExceptionMappingConfig findExceptionMappingConfig(
217 final Throwable throwable) {
218
219 return getExceptionMappingConfigs()
220 .stream()
221 .filter(exceptionMappingConfig -> matches(
222 throwable, exceptionMappingConfig.getExceptionClassName()))
223 .findFirst()
224 .orElseGet(this::getDefaultExceptionMappingConfig);
225 }
226
227 private boolean matches(final Throwable throwable, final String exceptionClassName) {
228 if (throwable == null || exceptionClassName == null) {
229 return false;
230 }
231 if (throwable.getClass().getName().equals(exceptionClassName)) {
232 return true;
233 }
234 if (exceptionClassName.endsWith(".*")) {
235 final String packagePrefix = exceptionClassName.substring(0, exceptionClassName.length() - 1);
236 if (throwable.getClass().getName().startsWith(packagePrefix)) {
237 return true;
238 }
239 }
240 if (matches(throwable.getCause(), exceptionClassName)) {
241 return true;
242 }
243 return matches(throwable.getClass().getSuperclass(), exceptionClassName);
244 }
245
246 private boolean matches(final Class<?> exceptionClass, final String exceptionClassName) {
247 if (exceptionClass == null || exceptionClassName == null) {
248 return false;
249 }
250 if (exceptionClass.getName().equals(exceptionClassName)) {
251 return true;
252 }
253 if (exceptionClassName.endsWith(".*")) {
254 final String packagePrefix = exceptionClassName.substring(0, exceptionClassName.length() - 1);
255 if (exceptionClass.getName().startsWith(packagePrefix)) {
256 return true;
257 }
258 }
259 return matches(exceptionClass.getSuperclass(), exceptionClassName);
260 }
261
262
263
264
265 @ToString
266 @EqualsAndHashCode
267 @NoArgsConstructor
268 @AllArgsConstructor
269 public static class ExceptionMapping {
270
271
272
273
274
275
276
277
278 @SuppressWarnings("WeakerAccess")
279 public ExceptionMapping(String exceptionClassName, HttpStatus httpStatus, String code) {
280 this.exceptionClassName = exceptionClassName;
281 if (httpStatus != null) {
282 this.status = httpStatus.value();
283 this.message = httpStatus.getReasonPhrase();
284 }
285 this.code = code;
286 }
287
288 @Getter
289 @Setter
290 private String exceptionClassName;
291
292 @Setter
293 private int status;
294
295 @Getter
296 @Setter
297 private String message;
298
299 @Getter
300 @Setter
301 private String code;
302
303
304
305
306
307
308 public int getStatus() {
309 if (HttpStatus.resolve(status) == null) {
310 return HttpStatus.INTERNAL_SERVER_ERROR.value();
311 }
312 return status;
313 }
314
315 }
316
317
318
319
320 @SuppressWarnings("WeakerAccess")
321 @ToString
322 @EqualsAndHashCode
323 @NoArgsConstructor
324 @AllArgsConstructor
325 public static class ExceptionMappingConfig {
326
327 @Getter
328 @Setter
329 private String exceptionClassName;
330
331 @Getter
332 @Setter
333 private boolean includeExceptionClassName = true;
334
335 @Getter
336 @Setter
337 private boolean includeApplicationName = true;
338
339 @Getter
340 @Setter
341 private boolean includePath = true;
342
343 @Getter
344 @Setter
345 private boolean includeHandler = false;
346
347 @Getter
348 @Setter
349 private boolean includeStackTrace = false;
350
351 @Getter
352 @Setter
353 private boolean includeCause = true;
354
355 @Getter
356 @Setter
357 private boolean evaluateAnnotationFirst = false;
358
359 }
360
361 }