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.List;
20 import org.immutables.value.Value;
21 import org.springframework.http.HttpStatus;
22 import org.springframework.lang.Nullable;
23
24
25
26
27
28
29 @Value.Immutable
30 public interface RestApiExceptionMapperProperties {
31
32
33
34
35
36
37 static ImmutableRestApiExceptionMapperProperties.Builder builder() {
38 return ImmutableRestApiExceptionMapperProperties.builder();
39 }
40
41
42
43
44
45
46 @Value.Default
47 default ExceptionMapping getDefaultExceptionMapping() {
48 return ExceptionMapping.builder()
49 .message(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase())
50 .status(HttpStatus.INTERNAL_SERVER_ERROR.value())
51 .exceptionClassName("*")
52 .build();
53 }
54
55
56
57
58
59
60 @Value.Default
61 default List<ExceptionMapping> getExceptionMappings() {
62 return List.of(
63 ExceptionMapping.builder()
64 .exceptionClassName(IllegalArgumentException.class.getName())
65 .status(HttpStatus.BAD_REQUEST.value())
66 .message(HttpStatus.BAD_REQUEST.getReasonPhrase())
67 .build(),
68 ExceptionMapping.builder()
69 .exceptionClassName("org.springframework.security.access.AccessDeniedException")
70 .status(HttpStatus.FORBIDDEN.value())
71 .message(HttpStatus.FORBIDDEN.getReasonPhrase())
72 .build(),
73 ExceptionMapping.builder()
74 .exceptionClassName("javax.persistence.EntityNotFoundException")
75 .status(HttpStatus.NOT_FOUND.value())
76 .message(HttpStatus.NOT_FOUND.getReasonPhrase())
77 .build()
78 );
79 }
80
81
82
83
84
85
86 @Value.Default
87 default ExceptionMappingConfig getDefaultExceptionMappingConfig() {
88 return ExceptionMappingConfig.builder()
89 .exceptionClassName("*")
90 .build();
91 }
92
93
94
95
96
97
98 @Value.Default
99 default List<ExceptionMappingConfig> getExceptionMappingConfigs() {
100 return List.of();
101 }
102
103
104
105
106
107
108
109 default ExceptionMapping findExceptionMapping(Throwable throwable) {
110 return getExceptionMappings()
111 .stream()
112 .filter(exceptionMapping -> matches(
113 throwable, exceptionMapping.getExceptionClassName()))
114 .findFirst()
115 .orElseGet(this::getDefaultExceptionMapping);
116 }
117
118
119
120
121
122
123
124 default ExceptionMappingConfig findExceptionMappingConfig(Throwable throwable) {
125 return getExceptionMappingConfigs()
126 .stream()
127 .filter(exceptionMappingConfig -> matches(
128 throwable, exceptionMappingConfig.getExceptionClassName()))
129 .findFirst()
130 .orElseGet(this::getDefaultExceptionMappingConfig);
131 }
132
133 private boolean matches(Throwable throwable, String exceptionClassName) {
134 if (throwable == null || exceptionClassName == null) {
135 return false;
136 }
137 if (throwable.getClass().getName().equals(exceptionClassName)) {
138 return true;
139 }
140 if (exceptionClassName.endsWith(".*")) {
141 String packagePrefix = exceptionClassName.substring(0, exceptionClassName.length() - 1);
142 if (throwable.getClass().getName().startsWith(packagePrefix)) {
143 return true;
144 }
145 }
146 if (matches(throwable.getCause(), exceptionClassName)) {
147 return true;
148 }
149 return matches(throwable.getClass().getSuperclass(), exceptionClassName);
150 }
151
152 private boolean matches(Class<?> exceptionClass, String exceptionClassName) {
153 if (exceptionClass == null || exceptionClassName == null) {
154 return false;
155 }
156 if (exceptionClass.getName().equals(exceptionClassName)) {
157 return true;
158 }
159 if (exceptionClassName.endsWith(".*")) {
160 String packagePrefix = exceptionClassName.substring(0, exceptionClassName.length() - 1);
161 if (exceptionClass.getName().startsWith(packagePrefix)) {
162 return true;
163 }
164 }
165 return matches(exceptionClass.getSuperclass(), exceptionClassName);
166 }
167
168
169
170
171 @Value.Immutable
172 interface ExceptionMapping {
173
174
175
176
177
178
179 static ImmutableExceptionMapping.Builder builder() {
180 return ImmutableExceptionMapping.builder();
181 }
182
183
184
185
186
187
188 String getExceptionClassName();
189
190
191
192
193
194
195 @Value.Default
196 default int getStatus() {
197 return HttpStatus.INTERNAL_SERVER_ERROR.value();
198 }
199
200
201
202
203
204
205 @Nullable
206 String getMessage();
207
208
209
210
211
212
213 @Nullable
214 String getCode();
215
216 }
217
218
219
220
221 @Value.Immutable
222 interface ExceptionMappingConfig {
223
224
225
226
227
228
229 static ImmutableExceptionMappingConfig.Builder builder() {
230 return ImmutableExceptionMappingConfig.builder();
231 }
232
233
234
235
236
237
238 String getExceptionClassName();
239
240
241
242
243
244
245 @Value.Default
246 default Boolean getIncludeMessage() {
247 return true;
248 }
249
250
251
252
253
254
255 @Value.Default
256 default Boolean getIncludeException() {
257 return true;
258 }
259
260
261
262
263
264
265 @Value.Default
266 default Boolean getIncludeApplicationName() {
267 return true;
268 }
269
270
271
272
273
274
275 @Value.Default
276 default Boolean getIncludePath() {
277 return true;
278 }
279
280
281
282
283
284
285 @Value.Default
286 default Boolean getIncludeHandler() {
287 return false;
288 }
289
290
291
292
293
294
295 @Value.Default
296 default Boolean getIncludeStackTrace() {
297 return false;
298 }
299
300
301
302
303
304
305 @Value.Default
306 default Boolean getIncludeCause() {
307 return false;
308 }
309
310
311
312
313
314
315 @Value.Default
316 default Boolean getEvaluateAnnotationFirst() {
317 return false;
318 }
319
320 }
321
322 }