Reputation: 279890
I have a HandlerInterceptorAdapter
that intercepts all requests and performs user authorization checks. Very basically:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
User user = ... // get user
checkIfAuthorized(user); // throws AuthorizationException
return true;
}
I then have an @ExceptionHandler
for that AuthorizationException
.
@ExceptionHandler(value = AuthorizationException.class)
public ResponseEntity<String> handleNotAuthorized(AuthorizationException e) {
// TODO Custom EXCEPTION HANDLER for json/jsp/xml/other types, based on content type
ResponseEntity<String> responseEntity = new ResponseEntity<>("You are not authorized to access that page.", HttpStatus.UNAUTHORIZED);
return responseEntity;
}
This is fine if the (unauthorized) request accepts text/plain
(and can be easily changed for json).
How can I make different @ExceptionHandler
s for specific Accept
headers?
@RequestMapping
has produces()
. Is there something similar for @ExceptionHandler
?
Upvotes: 6
Views: 6119
Reputation:
Not exactly the same use case, but the same requirement. I solve it with a custom HttpMessageConverter implementation.
@RestController
@RequestMapping("/foo")
public class MyResource {
@GetMapping(path = "/{id}", produces = "application/json")
public ResponseEntity<MyDto> get (@PathVariable(ID) long id)
throws IOException {
throw new MyCustomException();
}
@GetMapping(path = "/{id}/export", produces = "application/zip")
public ResponseEntity<byte[]> export (@PathVariable(ID) long id)
throws IOException {
throw new MyCustomException();
}
}
...
@ControllerAdvice
public class MyCustomExceptionHandler {
@ResponseBody
@ExceptionHandler
@ResponseStatus(BAD_REQUEST)
public JsonAPIErrorDocument handleException (MyCustomException e) {
return ....;
}
}
...
public class JsonAPIErrorDocumentToByteArrayMessageConverter extends AbstractHttpMessageConverter {
public ErrorDocumentToByteArrayMessageConverter () {
super(new MediaType("application", "zip"), MediaType.ALL);
}
@Override
protected boolean supports (Class clazz) {
return JsonAPIErrorDocument.class == clazz;
}
@Override
protected Object readInternal (Class clazz, HttpInputMessage inputMessage)
throws IOException,
HttpMessageNotReadableException {
return new byte[0];
}
@Override
protected void writeInternal (Object t, HttpOutputMessage outputMessage)
throws IOException,
HttpMessageNotWritableException {
}
}
...
@EnableWebMvc
@Configuration
@ComponentScan({ "com.foo" })
public class ApplicationConfig implements WebMvcConfigurer {
...
@Override
public void configureMessageConverters (List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
converters.add(new ByteArrayHttpMessageConverter());
converters.add(new JsonAPIErrorDocumentToByteArrayMessageConverter());
}
...
}
Upvotes: 0
Reputation: 51
I know this comes late but I've been looking up a solution to this, came across this question and found what I think to be a better solution. You can return "forward:/error" in your @ExceptionHandler (returning a String) to forward the request to a
@RequestMapping("/error")
ErrorController {...}
and use
@RequestMapping(produces = "text/html")
ModelAndView errorPage() {...}
on one method of that ErrorController,
@RequestMapping(produces = "application/json") // or no 'produces' attribute for a default
MyJsonObject errorJson() {...} on another.
I think this is a pretty neat way to do it, it's probably already out there but I didn't find it when trying to look it up.
So basically the @ExceptionHandler is the same for all, but forwards to a controller that can do the usual stuff
Upvotes: 3
Reputation: 8582
I think of two approaches:
Manually
public ResponseEntity<String> handleNotAuthorized(AuthorizationException e, HttpServletRequest request) {
// TODO Custom EXCEPTION HANDLER for json/jsp/xml/other types, based on content type
if (/*read header accept from request and build appropiate response*/) {}
ResponseEntity<String> responseEntity = new ResponseEntity<>("You are not authorized to access that page.", HttpStatus.UNAUTHORIZED);
return responseEntity;
Automatically
@ResponseBody
public SomeObject handleNotAuthorized(AuthorizationException e, HttpServletRequest request) {
// TODO Custom EXCEPTION HANDLER for json/jsp/xml/other types, based on content type
/* Construct someObject and let Spring MessageConverters transform it to JSON or XML. I don't remember what happens in case of HTML (it should go to a view)*/
return someObject;
Don't forget to set the Response's Status code.
Upvotes: 2