#ModelAttribute method handles #RequestBody object throws HttpMessageNotReadableException - java

In #ControllerAdvice class, I just want get request body. I have no idea better than handle it inside a #ModelAttribute method and set #RequestBody value into props. So, problem here is when I open #RequestBody it still get request body as expected, but after that it throws HttpMessageNotReadableException.
Someone can tell me the reason and have idea to resolve problem. It really means to me.
#ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler
{
public CustomRestExceptionHandler() {
super();
}
private Object request;
#ModelAttribute
public void setRequest(#RequestBody Object request) {
this.request = request;
}
#Override protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
logger.info(this.request)
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return new ResponseEntity<Object>(ex, HttpStatus.BAD_REQUEST);
}
}

I believe the reason you are getting HttpMessageNotReadableException is because you are trying to map JSON to an object using #RequestBody annotation twice. First in the ControllerAdvice model attribute then in the controller method parameter. Assuming you have a controller which looks like this
#RestController
public class MyController{
#PostMapping("endpoint")
public Response processRequest(#RequestBody MyObject myRequest)
System.out.println("bla bla");
}
Try removing the #RequestBody annotation and change it to
#PostMapping("endpoint")
public Response processRequest(final MyObject myRequest)
System.out.println("bla bla");
}
Then modify you Controller Advice to look like this:
#ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler
{
public CustomRestExceptionHandler() {
super();
}
private MyObject request;
#ModelAttribute("myRequest") //same as in controller
public MyObject setRequest(#RequestBody MyObject request) {
this.request = request;
return request;
}
#Override protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
logger.info(this.request)
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return new ResponseEntity<Object>(ex, HttpStatus.BAD_REQUEST);
}
}
Your ModelAttribute now returns the converted object so it can be passed on to the controller. Hope it solves your problem.

Related

No Content in Spring Boot Rest

How do I configure Spring Boot to return 204 in GET methods (typically findAll methods) when the method does not fetch records? I would not like to do treatment in each method, type the code below:
if(!result)
return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
return new ResponseEntity<Void>(HttpStatus.OK)
I'd like to transform this method:
#GetMapping
public ResponseEntity<?> findAll(){
List<User> result = service.findAll();
return !result.isEmpty() ?
new ResponseEntity<>(result, HttpStatus.OK) : new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}
In this one:
#GetMapping
public List<User> findAll(){
return service.findAll();
}
If the result from findAll() is empty or null then my controller should return 204 instead of 200.
You could register a custom ResponseBodyAdvice which allows customizing the response of #ResponseBody or ResponseEntity handler methods (right before the content is being serialized by a MessageConverter):
#ControllerAdvice
class NoContentControllerAdvice implements ResponseBodyAdvice<List<?>> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return List.class.isAssignableFrom(returnType.getParameterType());
}
#Override
public List<?> beforeBodyWrite(List<?> body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body.isEmpty()) {
response.setStatusCode(HttpStatus.NO_CONTENT);
}
return body;
}
}

Escape quotes in java spring request body

I have a Java Spring controller.
I want to escape all quotes in my request (sanitize it for using it in SQL queries for example).
Is there a way to do that with Spring ?
Example :
#RequestMapping(method = RequestMethod.POST)
public List<String[]> myEndpoint(#RequestBody Map<String, String> params, #AuthenticationPrincipal Account connectedUser) throws Exception{
return myService.runQuery(params, connectedUser);
}
If you want to validate all your request parameters in controllers, you can use custom validators. For Complete info, check Complete Example
Brief Overview:
Validator Implementation
#Component
public class YourValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return clazz.isAssignableFrom(YourPojoType.class);
}
#Override
public void validate(Object target, Errors errors) {
if (target instanceof YourPojoType) {
YourPojoType req = (YourPojoType) target;
Map<String, String> params = req.getParams();
//Do your validations.
//if any validation failed,
errors.rejectValue("yourFieldName", "YourCustomErrorCode", "YourCustomErrorMessage");
}
}
}
Controller
#RestController
public class YourController{
#Autowired
private YourValidator validator;
#RequestMapping(method = RequestMethod.POST)
public List<String[]> myEndpoint(#Valid YourPojoType req, BindingResult result, #AuthenticationPrincipal Account connectedUser) throws Exception{
if (result.hasErrors()) {
//throw exception
}
return myService.runQuery(params, connectedUser);
}
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
}

#RequestBody #Valid SomeDTO has field of enum type, custom error message

I have the following #RestController
#RequestMapping(...)
public ResponseEntity(#RequestBody #Valid SomeDTO, BindingResult errors) {
//do something with errors if validation error occur
}
public class SomeDTO {
public SomeEnum someEnum;
}
If the JSON request is { "someEnum": "valid value" }, everything works fine. However, if the request is { "someEnum": "invalid value" }, it only return error code 400.
How can I trap this error so I can provide a custom error message, such as "someEnum must be of value A/B/C".
The answer provided by #Amit is good and works. You can go ahead with that if you want to deserialize an enum in a specific way. But that solution is not scalable. Because every enum which needs validation must be annotated with #JsonCreator.
Other answers won't help you beautify the error message.
So here's my solution generic to all the enums in spring web environment.
#RestControllerAdvice
public class ControllerErrorHandler extends ResponseEntityExceptionHandler {
public static final String BAD_REQUEST = "BAD_REQUEST";
#Override
public ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException exception,
HttpHeaders headers, HttpStatus status, WebRequest request) {
String genericMessage = "Unacceptable JSON " + exception.getMessage();
String errorDetails = genericMessage;
if (exception.getCause() instanceof InvalidFormatException) {
InvalidFormatException ifx = (InvalidFormatException) exception.getCause();
if (ifx.getTargetType()!=null && ifx.getTargetType().isEnum()) {
errorDetails = String.format("Invalid enum value: '%s' for the field: '%s'. The value must be one of: %s.",
ifx.getValue(), ifx.getPath().get(ifx.getPath().size()-1).getFieldName(), Arrays.toString(ifx.getTargetType().getEnumConstants()));
}
}
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setTitle(BAD_REQUEST);
errorResponse.setDetail(errorDetails);
return handleExceptionInternal(exception, errorResponse, headers, HttpStatus.BAD_REQUEST, request);
}
}
This will handle all the invalid enum values of all types and provides a better error message for the end user.
Sample output:
{
"title": "BAD_REQUEST",
"detail": "Invalid enum value: 'INTERNET_BANKING' for the field: 'paymentType'. The value must be one of: [DEBIT, CREDIT]."
}
#ControllerAdvice
public static class GenericExceptionHandlers extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException e, HttpHeaders headers, HttpStatus status, WebRequest request) {
return new ResponseEntity<>(new ErrorDTO().setError(e.getMessage()), HttpStatus.BAD_REQUEST);
}
}
I created a fully functional Spring boot Application with a Test on Bitbucket
You do not need #Valid for enum validation, you can achieve the required response using below code:
Controller Code, StackDTO has an enum PaymentType in it:
#RequestMapping(value = "/reviews", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<String> add(#RequestBody StackDTO review) {
return new ResponseEntity<String>(HttpStatus.ACCEPTED);
}
Create an exception class, as EnumValidationException
public class EnumValidationException extends Exception {
private String enumValue = null;
private String enumName = null;
public String getEnumValue() {
return enumValue;
}
public void setEnumValue(String enumValue) {
this.enumValue = enumValue;
}
public String getEnumName() {
return enumName;
}
public void setEnumName(String enumName) {
this.enumName = enumName;
}
public EnumValidationException(String enumValue, String enumName) {
super(enumValue);
this.enumValue = enumValue;
this.enumName = enumName;
}
public EnumValidationException(String enumValue, String enumName, Throwable cause) {
super(enumValue, cause);
this.enumValue = enumValue;
this.enumName = enumName;
}
}
I have enum as below, with a special annotation #JsonCreator on a method create
public enum PaymentType {
CREDIT("Credit"), DEBIT("Debit");
private final String type;
PaymentType(String type) {
this.type = type;
}
String getType() {
return type;
}
#Override
public String toString() {
return type;
}
#JsonCreator
public static PaymentType create (String value) throws EnumValidationException {
if(value == null) {
throw new EnumValidationException(value, "PaymentType");
}
for(PaymentType v : values()) {
if(value.equals(v.getType())) {
return v;
}
}
throw new EnumValidationException(value, "PaymentType");
}
}
Finally RestErrorHandler class,
#ControllerAdvice
public class RestErrorHandler {
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ResponseEntity<ValidationErrorDTO> processValidationIllegalError(HttpMessageNotReadableException ex,
HandlerMethod handlerMethod, WebRequest webRequest) {
EnumValidationException exception = (EnumValidationException) ex.getMostSpecificCause();
ValidationErrorDTO errorDTO = new ValidationErrorDTO();
errorDTO.setEnumName(exception.getEnumName());
errorDTO.setEnumValue(exception.getEnumValue());
errorDTO.setErrorMessage(exception.getEnumValue() + " is an invalid " + exception.getEnumName());
return new ResponseEntity<ValidationErrorDTO>(errorDTO, HttpStatus.BAD_REQUEST);
}
}
ValidationErrorDTO is the dto with setters/getters of enumValue, enumName and errorMessage. Now when you send POST call to controller endpoint /reviews with below request
{"paymentType":"Credit2"}
Then code returns response as 400 with below response body -
{
"enumValue": "Credit2",
"enumName": "PaymentType",
"errorMessage": "Credit2 is an invalid PaymentType"
}
Let me know if it resolves your issue.
Yon can achieve this using #ControllerAdvice as follows
#org.springframework.web.bind.annotation.ExceptionHandler(value = {InvalidFormatException.class})
public ResponseEntity handleIllegalArgumentException(InvalidFormatException exception) {
return ResponseEntity.badRequest().body(exception.getMessage());
}
Basically , the idea is to catch com.fasterxml.jackson.databind.exc.InvalidFormatException and handle it as per your requirement.
#Valid has to do with Hibernate bean validation. Currently enum type is not supported out of the box. I found this answer to be the closet, https://funofprograming.wordpress.com/2016/09/29/java-enum-validator/, the drawback however is that you have to make the enum field of type String instead.

Inject request attribute from request to spring controller methods

I have some spring #RestControllers methods that I would like to inject with a value that comes with every request as a request attribute(containing the user) something like:
#RestController
#RequestMapping("/api/jobs")
public class JobsController {
// Option 1 get user from request attribute as prop somehow
private String userId = "user1";
// Option 2 inject into method using aspect or something else
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs() throws ResourceNotFoundException {
// currentUser is injected
this.getJobs(currentUser);
}
I know I can do that:
#RestController
#RequestMapping("/api/jobs")
public class JobsController {
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs(HttpServletRequest request) throws ResourceNotFoundException {
String currentUser = null;
if (request.getAttribute("subject") != null) {
currentUser = request.getAttribute("subject").toString();
}
this.getJobs(currentUser);
}
But that would require me to add this code at every method in my program, which seems to me, to be a really bad practice.
Is there a way to achieve what I want?
If the answer do require aspect, a code example will be much appreciated since I only read about it, but never actually did something with aspect.
Update
The code i suggested can be simplified using this:
#RestController
#RequestMapping("/api/jobs")
public class JobsController {
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs(#Value("#{request.getAttribute('subject')}" String currentUser) throws ResourceNotFoundException {
this.getJobs(currentUser);
}
But still require me to add that parameter at every method.
Can this parameter be injected to every method somehow?
You could use a Filter to populate a ThreadLocal<String> variable that stores that attribute:
public class MyFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
ContextHolder.setSubject(request.getAttribute('subject'));
chain.doFilter(request, response);
}
#Override
public void destroy() {
ContextHolder.removeSubject();
}
}
public class ContextHolder {
private static final ThreadLocal<String> SUBJECT = new ThreadLocal<String>() {
#Override
protected String initialValue() {
return "empty";
}
};
public static void setSubject(String subject) {
SUBJECT.set(subject);
}
public static String getSubject() {
return SUBJECT.get();
}
public static void removeSubject() {
SUBJECT.remove();
}
}
The filter will be configured to intercept all requests and populate the SUBJECT variable. By using a ThreadLocal, you make sure that each thread has it's own subject value. You can now get that value anywhere in your application by calling ContextHolder.getSubject():
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs(HttpServletRequest request) throws ResourceNotFoundException {
this.getJobs(ContextHolder.getSubject());
}
You will also have to register the Filter in the web.xml file:
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
In case you had multiple attributes, you could use a ThreadLocal<Map<String, String>> variable instead.
Simply just add #ResuestAttribute in your rest contorller
#RestController
#RequestMapping(path="/yourpath")
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity getAll(
#RequestAttribute(value = "yourAttribute") Object
yourAttribute......
If you really want to know about attributes then you should check out spring's #RequestParam annotation. You'd use it like this:
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs(#RequestParam("subject") String currentUser) throws ResourceNotFoundException {
this.getJobs(currentUser);
}

Intercept null ResponseBody before marshalling response

I've got multiple controllers for RESTful endpoints which currently return null if there's no resource at the endpoint. For instance,
#RequestMapping(method = ReqeustMethod.GET, value = "{id}")
#ResponseBody
public MyResource get(#PathVariable final Long id) {
return this.myService.get(id); // returns null if bad id
}
I want to return a specific, different resource to the client (ErrorResource) when there's no MyResource with the given id. I know I can do that with a separate method with #ExceptionHandler, such as:
#RequestMapping(method = RequestMethod.GET, value = "{id}")
#ResponseBody
public MyResource get(#PathVariable final Long id) {
final MyResource myResource = this.myService.get(id);
if (myResource == null) {
throw new NotFoundException();
}
return myResource;
}
#ExceptionHandler(NotFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorResource notFoundException(
final HttpServletRequest request,
final NotFoundException exception) {
final ErrorResource errorResource = new ErrorResource();
errorResource.setStatus(HttpStatus.NOT_FOUND.value());
errorResource.setDeveloperMessage("No resource found at " + request.getRequestURL());
return errorResource;
}
And that's nice. But what I'd really like to be able to do is have some kind of interceptor that figures out for me that whenever an API method is returning a null #ResponseBody, it should instead run the logic in my notFoundException() method. That will make all my controller methods a little cleaner. Is there any way to do that?
It sounds like a job for Spring's HttpMessageConverter.
You can write your own converter by implementing HttpMessageConverter<T> interface.
In your case I would implement a converter of HttpMessageConverter<MyResource> with a null check on the MyResource instance in the write method. If the MyResource instance is null, then build and write your ErrorResource instance.
Here is an example:
import java.io.IOException;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
public class MyResourceConverter implements HttpMessageConverter<MyResource> {
// a real message converter that will respond to ancillary methods and do the actual work
private HttpMessageConverter<Object> delegateConverter;
public MyResourceConverter(HttpMessageConverter<Object> delegateConverter){
this.delegateConverter = delegateConverter;
}
#Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return delegateConverter.canRead(clazz, mediaType) && MyResource.class.equals(clazz);
}
#Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return delegateConverter.canWrite(clazz, mediaType) && MyResource.class.equals(clazz);
}
#Override
public MyResource read(Class<? extends MyResource> clazz,
HttpInputMessage inputMessage) throws IOException,
HttpMessageNotReadableException {
return (MyResource) delegateConverter.read(clazz, inputMessage);
}
#Override
public void write(MyResource t, MediaType contentType,
HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
Object result = null;
if(t == null){
result = // build your ErrorResource here
}else{
result = t;
}
delegateConverter.write(result, contentType, outputMessage);
}
}
Note that this converter needs to be registered in your Spring configuration.
The configuration class must extend WebMvcConfigurerAdapter and override the configureMessageConverters method, like:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// Here we add our custom-configured HttpMessageConverters.
// yourDelegateConverter may be a MappingJackson2HttpMessageConverter instance for example
converters.add(new EmployeeConverter(yourDelegateConverter));
super.configureMessageConverters(converters);
}
References (from official Spring documentation):
HTTP Message conversion
HttpMessageConverter
WebMvcConfigurerAdapter

Categories