No Content in Spring Boot Rest - java

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;
}
}

Related

SpringBoot: Wrap the Controller Response into Another Object

Consider the below Controller method,
#GetMapping("/getdata")
public Data getDetails() {
try {
Data obj = template.getForObject("http://localhost:8090/details/get", Data.class);
return obj;
} catch (Exception e) {
.....
}
}
Let's say the response object for the above endpoint looks like,
data : {
name : "ABCD",
age : "20"
}
Now what I am trying to achieve is to Wrap the entire response object under another object, so the final response should look like,
{
status : "SOMETHING",
response : {
name : "ABCD",
age : "20"
},
extra : null
}
So the issue is, I don't want to create a function under each controller method to sent values to this Wrapper Object. My question is, Is there any possibility in Spring Framework that allows me to create a Global Wrapper function somewhere, and it will automatically pick it and wrap the response from the controller?
You will need to implement a ResponseBodyAdvice as a #ControllerAdvice. First, you need to have a model for your generic response.
public class GenericResponse<T> {
private String status;
private String extra;
private T response;
}
Then you need to implement the ResponseBodyAdvice itself:
#ControllerAdvice
public class CustomResponseBodyAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object response, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
GenericResponse<Object> genericResponse = new GenericResponse<>();
output.setStatus("SOMETHING"); // I guess that you need some logic here
output.setResponse(response);
return genericResponse;
}
}

#ModelAttribute method handles #RequestBody object throws HttpMessageNotReadableException

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.

how to get request session before controller method

I have a server and simple auth using controller. How to get request session and modify response body before running the controller method?
I want to check(for only methods marked by my own annotation) if session have an attribute (login) or not, and to send my error response body or go to the controller.
I tried to use aspectj and interceptor but as a result I can manage session after the controller method has been completed. Because interceptor preHandle method runs before aspectJ #Before.
#Retention(RUNTIME)
#Target({ TYPE, METHOD })
public #interface CheckSession {
}
-
#Aspect
#Component
public class CheckSessionAspect {
public CheckSessionAspect() {
super();
}
#Before("#annotation(path.to.CheckSession)")
public void check(JoinPoint joinPoint) throws Throwable {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
requestAttributes.setAttribute(SESSION_MARKER, "1", RequestAttributes.SCOPE_REQUEST);
}
}
-
#ControllerAdvice
public class ResponseModifierAdvice implements ResponseBodyAdvice<Object> {
#Autowired
ErrorService errorService;
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
if (servletRequest.getAttribute(SESSION_MARKER) != null)
if (servletRequest.getSession().getAttribute(LOGIN) == null || StringUtils.isBlank(servletRequest.getSession().getAttribute(LOGIN).toString())) {
ResponseEntity<Object> newResponseBody = errorService.sessionError(request, returnType.getMethod().getName(), NO_SESSION, getClass().toString());
body = newResponseBody.getBody();
}
return body;
}
}
I did it. You can use #Around and return ResponseEntity object if the user has no session.
#Aspect
#Component
public class CheckSessionAspect {
public CheckSessionAspect() {
super();
}
#Around("#annotation(path.to.CheckSession)")
public Object check(ProceedingJoinPoint pjp) throws Throwable {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
if ((request.getSession().getAttribute(LOGIN) == null || StringUtils.isBlank(request.getSession().getAttribute(LOGIN).toString())) ||
(request.getSession().getAttribute(IP) == null || StringUtils.isBlank(request.getSession().getAttribute(IP).toString()) ||
request.getSession().getAttribute(IP) != null && !request.getSession().getAttribute(IP).toString().equals(request.getRemoteAddr())))
{
requestAttributes.setAttribute(WRONG_SESSION_MARKER, "1", RequestAttributes.SCOPE_REQUEST);
return new ResponseEntity<Object>( new YourResponse(ERROR, "Invalid session"), HttpStatus.BAD_REQUEST);
}
Object proceed = pjp.proceed();
return proceed;
}
}

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

How can I use Spring REST with MappingJackson2XmlHttpMessageConverter and XSD validation

I'm creating a Spring 4 REST application, using MappingJackson2XmlHttpMessageConverter to convert incoming XML requests to domain objects. Is there any way to apply XSD validation in that process? If not, I think my fallback is to just make the #RequestBody a String, parse and validate it, and then convert it to the domain object. Is there a better approach?
One approach to this may be to write a custom HttpMessageConverter<T> that checks XSD validation (look here for a way to validate XML with XSD) before returning the object.
Suppose that you have the following method in your Controller class:
#RequestMapping(value = "/")
public CustomObject getCustomObject(#RequestParam(value = "id") String id){
return new CustomObject();
}
Then your converter may look like this:
public class CustomObjectConverter implements HttpMessageConverter<CustomObject> {
// a real message converter that will respond to ancillary methods and do the actual work
protected HttpMessageConverter<Object> delegateConverter;
public CustomObjectConverter (HttpMessageConverter<Object> delegate) {
super(delegate, personService);
super.delegateConverter = delegate;
this.employeePhotoBaseUrl = employeePhotoBaseUrl;
}
#Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return delegateConverter.canRead(clazz, mediaType) && CustomObject.class.equals(clazz);
}
#Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return delegateConverter.canWrite(clazz, mediaType) && CustomObject.class.equals(clazz);
}
#Override
public List<MediaType> getSupportedMediaTypes() {
return delegateConverter.getSupportedMediaTypes();
}
#Override
public CustomObject read(Class<? extends CustomObject> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return (CustomObject) delegateConverter.read(clazz, inputMessage);
}
#Override
public void write(CustomObject t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
if(validationOK)
delegateConverter.write(t, contentType, outputMessage);
else
// You may implement a custom exception handler to return a proper HTTP error code
throw new YourCustomException();
}
}
Remember to configure your new converter. I do this in my configuration class:
#Configuration
#EnableWebMvc
public class RestConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// initialize your MappingJackson2XmlHttpMessageConverter
MappingJackson2XmlHttpMessageConverter xmlMessageConverter = xmlMessageConverter();
//Here we add our custom-configured HttpMessageConverters
converters.add(new CustomObjectConverter(xmlMessageConverter));
super.configureMessageConverters(converters);
}
}

Categories