View Javadoc
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   * Defaults handlers methods
35   * 
36   * @author Fernando Romulo da Silva
37   */
38  abstract class AbstractRestExceptionHandler extends ResponseEntityExceptionHandler {
39  
40      protected final MessageSource messageSource;
41  
42      /**
43       * Default constructor.
44       * 
45       * @param messageSource Object used to translate messages
46       */
47      protected AbstractRestExceptionHandler(final MessageSource messageSource) {
48  	super();
49  	this.messageSource = messageSource;
50      }
51  
52      /**
53       * {@inheritDoc}
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       * {@inheritDoc}
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       * {@inheritDoc}
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      * {@inheritDoc}
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      * Handle exceptions, especially {@link BaseApplicationException} and its descendents and use the exception's message.
116      * 
117      * @param ex      The exception
118      * @param request Request object, to create response body.
119      * @param status  The error status
120      * @return A {@link ResponseEntity} object that's the response
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      * Handle exceptions using the message on response.
137      * 
138      * @param msg     The message that it'll be used
139      * @param ex      The exception
140      * @param request Request object, to create response body.
141      * @param status  The error status
142      * @return A {@link ResponseEntity} object that's the response
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 }