View Javadoc
1   /*
2   * Copyright 2020-2026 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.exception.spring.boot.autoconfigure;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  import lombok.AllArgsConstructor;
22  import lombok.Data;
23  import lombok.EqualsAndHashCode;
24  import lombok.Getter;
25  import lombok.NoArgsConstructor;
26  import lombok.Setter;
27  import lombok.ToString;
28  import org.bremersee.exception.RestApiExceptionMapperProperties;
29  import org.bremersee.exception.RestApiExceptionMapperProperties.ExceptionMapping;
30  import org.bremersee.exception.RestApiExceptionMapperProperties.ExceptionMappingConfig;
31  import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
32  import org.springframework.boot.context.properties.ConfigurationProperties;
33  import org.springframework.http.HttpStatus;
34  
35  /**
36   * Configuration properties for the rest api exception handler or resolver.
37   *
38   * @author Christian Bremer
39   */
40  @ConditionalOnClass(RestApiExceptionMapperProperties.class)
41  @ConfigurationProperties(prefix = "bremersee.exception-mapping")
42  @Getter
43  @Setter
44  @ToString
45  @EqualsAndHashCode
46  public class RestApiExceptionMapperBootProperties {
47  
48    /**
49     * The request paths the handler is responsible for. Default is empty.
50     */
51    private List<String> apiPaths = new ArrayList<>();
52  
53    /**
54     * The default values of a rest api exception that will be set, if a value is not detected. The
55     * default values are:
56     * <table style="border: 1px solid">
57     * <thead>
58     * <tr>
59     * <th style="border: 1px solid">attribute</th>
60     * <th style="border: 1px solid">value</th>
61     * </tr>
62     * </thead>
63     * <tbody>
64     * <tr>
65     * <td style="border: 1px solid">status</td>
66     * <td style="border: 1px solid">500</td>
67     * </tr>
68     * <tr>
69     * <td style="border: 1px solid">message</td>
70     * <td style="border: 1px solid">Internal Server Error</td>
71     * </tr>
72     * <tr>
73     * <td style="border: 1px solid">code</td>
74     * <td style="border: 1px solid">UNSPECIFIED</td>
75     * </tr>
76     * </tbody>
77     * </table>
78     */
79    private ExceptionMappingImpl defaultExceptionMapping;
80  
81    /**
82     * Values of errors whose values can not be determined automatically. The name of the
83     * exception can be a package, too (e.g. org.bremersee.foobar.*).
84     *
85     * <p>Examples application.yml:
86     * <pre>
87     * bremersee:
88     *   exception-mapping:
89     *     exception-mappings:
90     *     - exception-class-name: org.springframework.security.access.AccessDeniedException
91     *       status: 403
92     *       message: Forbidden
93     *       code: XYZ:0815
94     *     - exception-class-name: javax.persistence.EntityNotFoundException
95     *       status: 404
96     *       message: Not Found
97     *       code: GEN:404
98     * </pre>
99     */
100   private List<ExceptionMappingImpl> exceptionMappings = new ArrayList<>();
101 
102   /**
103    * The default configuration of the exception mapping.
104    *
105    * <table style="border: 1px solid">
106    * <thead>
107    * <tr>
108    * <th style="border: 1px solid">attribute</th>
109    * <th style="border: 1px solid">value</th>
110    * </tr>
111    * </thead>
112    * <tbody>
113    * <tr>
114    * <td style="border: 1px solid">includeException</td>
115    * <td style="border: 1px solid">true</td>
116    * </tr>
117    * <tr>
118    * <td style="border: 1px solid">includeApplicationName</td>
119    * <td style="border: 1px solid">true</td>
120    * </tr>
121    * <tr>
122    * <td style="border: 1px solid">includePath</td>
123    * <td style="border: 1px solid">true</td>
124    * </tr>
125    * <tr>
126    * <td style="border: 1px solid">includeHandler</td>
127    * <td style="border: 1px solid">false</td>
128    * </tr>
129    * <tr>
130    * <td style="border: 1px solid">includeStackTrace</td>
131    * <td style="border: 1px solid">false</td>
132    * </tr>
133    * <tr>
134    * <td style="border: 1px solid">includeCause</td>
135    * <td style="border: 1px solid">true</td>
136    * </tr>
137    * <tr>
138    * <td style="border: 1px solid">evaluateAnnotationFirst</td>
139    * <td style="border: 1px solid">false</td>
140    * </tr>
141    * </tbody>
142    * </table>
143    *
144    * <p>If two values of a key are available (e.g. per annotation (see {@link
145    * org.springframework.web.bind.annotation.ResponseStatus}) and member attribute) it is possible
146    * with {@code evaluateAnnotationFirst} to specify which one should be used.
147    */
148   private ExceptionMappingConfigImpl defaultExceptionMappingConfig;
149 
150   /**
151    * Specifies mapping configuration per exception class.  The name of the exception can be a
152    * package, too (e.g. org.bremersee.foobar.*).
153    *
154    * <p>Examples application.yml:
155    * <pre>
156    * bremersee:
157    *   exception-mapping:
158    *     exception-mapping-configs:
159    *     - exception-class-name: org.springframework.security.access.AccessDeniedException
160    *       include-exception-class-name: false
161    *       include-handler: true
162    *       include-cause: true
163    *     - exception-class-name: org.springframework.*
164    *       include-exception-class-name: true
165    *       include-handler: true
166    *       include-cause: false
167    * </pre>
168    */
169   private List<ExceptionMappingConfigImpl> exceptionMappingConfigs = new ArrayList<>();
170 
171   /**
172    * Instantiates rest api exception mapper properties.
173    */
174   public RestApiExceptionMapperBootProperties() {
175 
176     defaultExceptionMapping = new ExceptionMappingImpl();
177     defaultExceptionMapping.setExceptionClassName("*");
178     defaultExceptionMapping.setMessage(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
179     defaultExceptionMapping.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
180 
181     defaultExceptionMappingConfig = new ExceptionMappingConfigImpl();
182     defaultExceptionMappingConfig.setExceptionClassName("*");
183 
184     exceptionMappings.add(new ExceptionMappingImpl(
185         IllegalArgumentException.class.getName(),
186         HttpStatus.BAD_REQUEST,
187         null));
188 
189     exceptionMappings.add(new ExceptionMappingImpl(
190         "org.springframework.security.access.AccessDeniedException",
191         HttpStatus.FORBIDDEN,
192         null));
193 
194     exceptionMappings.add(new ExceptionMappingImpl(
195         "javax.persistence.EntityNotFoundException",
196         HttpStatus.NOT_FOUND,
197         null));
198   }
199 
200   /**
201    * To rest api exception mapper properties rest api exception mapper properties.
202    *
203    * @return the rest api exception mapper properties
204    */
205   public RestApiExceptionMapperProperties toRestApiExceptionMapperProperties() {
206     return RestApiExceptionMapperProperties.builder()
207         .defaultExceptionMapping(getDefaultExceptionMapping())
208         .exceptionMappings(getExceptionMappings())
209         .defaultExceptionMappingConfig(getDefaultExceptionMappingConfig())
210         .exceptionMappingConfigs(getExceptionMappingConfigs())
211         .build();
212   }
213 
214   /**
215    * The exception mapping.
216    */
217   @Setter
218   @ToString
219   @EqualsAndHashCode
220   @NoArgsConstructor
221   @AllArgsConstructor
222   public static class ExceptionMappingImpl implements ExceptionMapping {
223 
224     /**
225      * Instantiates a new exception mapping.
226      *
227      * @param exceptionClassName the exception class name
228      * @param httpStatus the http status
229      * @param code the code
230      */
231     public ExceptionMappingImpl(String exceptionClassName, HttpStatus httpStatus, String code) {
232       this.exceptionClassName = exceptionClassName;
233       if (httpStatus != null) {
234         this.status = httpStatus.value();
235         this.message = httpStatus.getReasonPhrase();
236       }
237       this.code = code;
238     }
239 
240     /**
241      * The exception class name.
242      */
243     @Getter
244     private String exceptionClassName;
245 
246     private int status;
247 
248     /**
249      * The exception message.
250      */
251     @Getter
252     private String message;
253 
254     /**
255      * The exception code.
256      */
257     @Getter
258     private String code;
259 
260     /**
261      * Gets status.
262      *
263      * @return the status
264      */
265     @Override
266     public int getStatus() {
267       if (HttpStatus.resolve(status) == null) {
268         return HttpStatus.INTERNAL_SERVER_ERROR.value();
269       }
270       return status;
271     }
272 
273   }
274 
275   /**
276    * The exception mapping config.
277    */
278   @Data
279   public static class ExceptionMappingConfigImpl implements ExceptionMappingConfig {
280 
281     private String exceptionClassName;
282 
283     private Boolean includeMessage = true;
284 
285     private Boolean includeException = true;
286 
287     private Boolean includeApplicationName = true;
288 
289     private Boolean includePath = true;
290 
291     private Boolean includeHandler = false;
292 
293     private Boolean includeStackTrace = false;
294 
295     private Boolean includeCause = false;
296 
297     private Boolean evaluateAnnotationFirst = false;
298 
299     /**
300      * Instantiates a new exception mapping config.
301      */
302     public ExceptionMappingConfigImpl() {
303       super();
304     }
305 
306   }
307 
308 }