1616
1717package org .springframework .samples .petclinic .rest .advice ;
1818
19+ import java .net .URI ;
20+ import java .time .Instant ;
21+ import java .util .List ;
22+ import java .util .Objects ;
23+
1924import jakarta .servlet .http .HttpServletRequest ;
25+ import org .slf4j .Logger ;
26+ import org .slf4j .LoggerFactory ;
2027import org .springframework .dao .DataIntegrityViolationException ;
2128import org .springframework .http .HttpStatus ;
2229import org .springframework .http .ProblemDetail ;
2330import org .springframework .http .ResponseEntity ;
2431import org .springframework .samples .petclinic .rest .controller .BindingErrorsResponse ;
32+ import org .springframework .samples .petclinic .rest .dto .ValidationMessageDto ;
2533import org .springframework .validation .BindingResult ;
2634import org .springframework .web .bind .MethodArgumentNotValidException ;
2735import org .springframework .web .bind .annotation .ControllerAdvice ;
2836import org .springframework .web .bind .annotation .ExceptionHandler ;
2937import org .springframework .web .bind .annotation .ResponseBody ;
3038
31- import java .net .URI ;
32- import java .time .Instant ;
33-
3439/**
3540 * Global Exception handler for REST controllers.
3641 * <p>
4247@ ControllerAdvice
4348public class ExceptionControllerAdvice {
4449
50+ private static final Logger logger = LoggerFactory .getLogger (ExceptionControllerAdvice .class );
51+ private static final String ERROR_UNEXPECTED = "An unexpected error occurred while processing your request" ;
52+ private static final String ERROR_DATA_INTEGRITY = "The requested resource could not be processed due to a data constraint violation" ;
53+ private static final String ERROR_INVALID_REQUEST = "The request contains invalid or missing parameters" ;
54+
4555 /**
4656 * Private method for constructing the {@link ProblemDetail} object passing the name and details of the exception
4757 * class.
4858 *
49- * @param ex Object referring to the thrown exception.
59+ * @param e Object referring to the thrown exception.
5060 * @param status HTTP response status.
5161 * @param url URL request.
5262 */
53- private ProblemDetail detailBuild (Exception ex , HttpStatus status , StringBuffer url ) {
54- ProblemDetail detail = ProblemDetail .forStatus (status );
55- detail .setType (URI .create (url .toString ()));
56- detail .setTitle (ex .getClass ().getSimpleName ());
57- detail .setDetail (ex .getLocalizedMessage ());
58- detail .setProperty ("timestamp" , Instant .now ());
59- return detail ;
63+ private ProblemDetail detailBuild (Exception e , HttpStatus status , StringBuffer url , String detail ) {
64+ ProblemDetail problemDetail = ProblemDetail .forStatus (status );
65+ problemDetail .setType (URI .create (url .toString ()));
66+ problemDetail .setTitle (e .getClass ().getSimpleName ());
67+ problemDetail .setDetail (detail );
68+ problemDetail .setProperty ("timestamp" , Instant .now ());
69+ problemDetail .setProperty ("schemaValidationErrors" , List .<ValidationMessageDto >of ());
70+ return problemDetail ;
6071 }
6172
6273 /**
@@ -69,50 +80,71 @@ private ProblemDetail detailBuild(Exception ex, HttpStatus status, StringBuffer
6980 @ ExceptionHandler (Exception .class )
7081 @ ResponseBody
7182 public ResponseEntity <ProblemDetail > handleGeneralException (Exception e , HttpServletRequest request ) {
83+ logger .error ("Unexpected error at {} {}" , request .getMethod (), request .getRequestURI (), e );
7284 HttpStatus status = HttpStatus .INTERNAL_SERVER_ERROR ;
73- ProblemDetail detail = this .detailBuild (e , status , request .getRequestURL ());
85+ ProblemDetail detail = this .detailBuild (e , status , request .getRequestURL (), ERROR_UNEXPECTED );
7486 return ResponseEntity .status (status ).body (detail );
7587 }
7688
7789 /**
7890 * Handles {@link DataIntegrityViolationException} which typically indicates database constraint violations. This
7991 * method returns a 404 Not Found status if an entity does not exist.
8092 *
81- * @param ex The {@link DataIntegrityViolationException} to be handled
93+ * @param e The {@link DataIntegrityViolationException} to be handled
8294 * @param request {@link HttpServletRequest} object referring to the current request.
8395 * @return A {@link ResponseEntity} containing the error information and a 404 Not Found status
8496 */
8597 @ ExceptionHandler (DataIntegrityViolationException .class )
8698 @ ResponseBody
87- public ResponseEntity <ProblemDetail > handleDataIntegrityViolationException (DataIntegrityViolationException ex , HttpServletRequest request ) {
99+ public ResponseEntity <ProblemDetail > handleDataIntegrityViolationException (DataIntegrityViolationException e , HttpServletRequest request ) {
100+ logger .warn ("Data integrity violation at {} {}: {}" ,
101+ request .getMethod (),
102+ request .getRequestURI (),
103+ e .getMessage ());
104+ logger .debug ("Data integrity violation stacktrace" , e );
88105 HttpStatus status = HttpStatus .NOT_FOUND ;
89- ProblemDetail detail = ProblemDetail .forStatus (status );
90- detail .setType (URI .create (request .getRequestURL ().toString ()));
91- detail .setTitle (ex .getClass ().getSimpleName ());
92- detail .setDetail ("Request could not be processed" );
93- detail .setProperty ("timestamp" , Instant .now ());
106+ ProblemDetail detail = this .detailBuild (e , status , request .getRequestURL (), ERROR_DATA_INTEGRITY );
94107 return ResponseEntity .status (status ).body (detail );
95108 }
96109
97110 /**
98111 * Handles exception thrown by Bean Validation on controller methods parameters
99112 *
100- * @param ex The {@link MethodArgumentNotValidException} to be handled
113+ * @param e The {@link MethodArgumentNotValidException} to be handled
101114 * @param request {@link HttpServletRequest} object referring to the current request.
102115 * @return A {@link ResponseEntity} containing the error information and a 400 Bad Request status.
103116 */
104117 @ ExceptionHandler (MethodArgumentNotValidException .class )
105118 @ ResponseBody
106- public ResponseEntity <ProblemDetail > handleMethodArgumentNotValidException (MethodArgumentNotValidException ex , HttpServletRequest request ) {
119+ public ResponseEntity <ProblemDetail > handleMethodArgumentNotValidException (MethodArgumentNotValidException e , HttpServletRequest request ) {
107120 HttpStatus status = HttpStatus .BAD_REQUEST ;
108121 BindingErrorsResponse errors = new BindingErrorsResponse ();
109- BindingResult bindingResult = ex .getBindingResult ();
122+ BindingResult bindingResult = e .getBindingResult ();
123+ ProblemDetail detail = this .detailBuild (e , status , request .getRequestURL (), ERROR_INVALID_REQUEST );
110124 if (bindingResult .hasErrors ()) {
111125 errors .addAllErrors (bindingResult );
112- ProblemDetail detail = this .detailBuild (ex , status , request .getRequestURL ());
126+ List <ValidationMessageDto > schemaValidationErrors = bindingResult .getFieldErrors ().stream ()
127+ .map (fieldError -> {
128+ String rejectedValue = Objects .toString (fieldError .getRejectedValue (), "null" );
129+ String defaultMessage = Objects .toString (fieldError .getDefaultMessage (), "Validation failed" );
130+ String message = "Field '%s' %s (rejected value: %s)" .formatted (
131+ fieldError .getField (),
132+ defaultMessage ,
133+ rejectedValue );
134+ return new ValidationMessageDto (message )
135+ .putAdditionalProperty ("field" , fieldError .getField ())
136+ .putAdditionalProperty ("rejectedValue" , rejectedValue )
137+ .putAdditionalProperty ("defaultMessage" , defaultMessage );
138+ })
139+ .toList ();
140+ logger .debug ("Validation error at {} {}: {}" ,
141+ request .getMethod (),
142+ request .getRequestURI (),
143+ bindingResult .getFieldErrors ());
144+ detail .setProperty ("schemaValidationErrors" , schemaValidationErrors );
113145 return ResponseEntity .status (status ).body (detail );
114146 }
115- return ResponseEntity .status (status ).build ( );
147+ return ResponseEntity .status (status ).body ( detail );
116148 }
117149
118150}
0 commit comments