How to change controller response in filter to make the response structure consistent all over the API's using spring-boot - servlets

I have implemented REST API using spring-boot application. All my API's are now returning response in JSON format for each entity. This response was consumed by other server which expects all these response are in same JSON format. For example;
All my responses should be accommodated within the following structure;
public class ResponseDto {
private Object data;
private int statusCode;
private String error;
private String message;
}
Currently spring-boot returns error responses in a different format. How to achieve this using filter.
Error message format;
{
"timestamp" : 1426615606,
"exception" : "org.springframework.web.bind.MissingServletRequestParameterException",
"status" : 400,
"error" : "Bad Request",
"path" : "/welcome",
"message" : "Required String parameter 'name' is not present"
}
I need both error and successfull response are in same json structure all over my spring-boot application

This can be achieved simply by utilizing a ControllerAdvice and handling all possible exceptions, then returning a response of your own choosing.
#RestControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(Throwable.class)
public ResponseDto handleThrowable(Throwable throwable) {
// can get details from throwable parameter
// build and return your own response for all error cases.
}
// also you can add other handle methods and return
// `ResponseDto` with different values in a similar fashion
// for example you can have your own exceptions, and you'd like to have different status code for them
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(CustomNotFoundException.class)
public ResponseDto handleCustomNotFoundException(CustomNotFoundException exception) {
// can build a "ResponseDto" with 404 status code for custom "not found exception"
}
}
Some great read on controller advice exception handlers

Related

Model is getting bound but not the individual parameters from json request body in ASP.NET Core Web API

Using postman, am trying to post "city" and "country" data.
URL: http://localhost:8080/api/Sample/SendData
RequestBody: {"city": "abc","country": "xyz"}
Headers: Content-Type: application/json
But am unable to retrieve the data instead getting nulls as show below. By encapsulating the properties (city, country) into a model, I am able to see the data.
Below is the code am using
[AllowAnonymous]
[ApiController]
public class SampleController : ControllerBase
{
private readonly ILogger<SampleController> _logger;
public SampleController(ILogger<SampleController> logger)
{
_logger = logger;
}
[HttpPost]
[Route("api/Sample/SendData")]
public ActionResult SendData(string city, string country)
{
try
{
if (ModelState.IsValid)
{
//return
return Ok("Success");
}
else
{
throw new Exception("error");
}
}
catch (Exception ex)
{
//return
return BadRequest(Convert.ToString(ex));
}
}
}
Note: I want to send the data using request body and not through query string as the data am going to pass eventually be larger.
It won't work with individual parameters. When you're using datatype json, the asp.net controller will always assume it's an object you're sending.
If you want individual parameters you could remove the json request header and replace it with text/plain and change your request body to key-value.
There's a similar question here but you would need to install newtonsoftjson nuget package. You'll only get 1 string though, which is the whole json file, then you'll be deserializing it to an object when it gets to the controller.
POST Json without model and Ajax

how to manage multiple http response status code while using spring message converters

In our api we are using spring Jackson http message converter to automatically convert java object to json. I am enjoying that feature,but what I personally feel is that I've lost control over the response http status code.if I want to return the response with different status codes ,I have the choice of using #responsestatus(httpstatus),but I cannot specify the status dynamically,as annotation is expecting a enum const expression. The other choice is http server response.set status(),but I don't like that.spring's responseentity(jsonstring,statuscode) is a great thing to solve but if I want to use Jackson httpmessageconverter is any way to configure the response status code dynamically.
You can return ResponseEntity<MyObject> from your controller method and it will still use the configured message converters, example:
#RestController
#RequestMapping("/foo")
public class FooController {
#RequestMapping
public ResponseEntity<MyObject> foo() {
MyObject myObject = new MyObject();
// You can dynamically set the status based on your needs
HttpStatus status = HttpStatus.OK;
return new ResponseEntity<>(myObject, status);
}
}

Spring MVC: Request Scope, trying to update a Command Object with binder.setDisallowedFields

I have this Object
public class Deportista implements Serializable {
private static final long serialVersionUID = 6229604242306465153L;
private String id;
...
#NotNull(message="{field.null}")
public String getId() {
return id;
}
...
}
I have the following Controller's methods
#InitBinder(value="deportistaRegistrar")
public void registrarInitBinder(WebDataBinder binder) {
logger.info(">>>>>>>> registrarInitBinder >>>>>>>>>>>>>");
}
#RequestMapping(value="/registrar.htm", method=RequestMethod.GET)
public String crearRegistrarFormulario(Model model){
logger.info("crearRegistrarFormulario GET");
Deportista deportista = new Deportista();
model.addAttribute("deportistaRegistrar", deportista);
return "deportista.formulario.registro";
}
#RequestMapping(value="/registrar.htm", method=RequestMethod.POST)
public String registrarPerson(#Validated #ModelAttribute("deportistaRegistrar") Deportista deportista,
BindingResult result){
logger.info("registrarPerson POST");
logger.info("{}", deportista.toString());
if(result.hasErrors()){
logger.error("There are errors!!!!");
for(ObjectError objectError : result.getAllErrors()){
logger.error("Error {}", objectError);
}
return "deportista.formulario.registro";
}
logger.info("All fine!!!!");
this.fakeMultipleRepository.insertDeportista(deportista);
return "redirect:/manolo.htm";
}
Until here the Controller is able to create a form (GET) and submit (POST) a new command object, Validation code works well.
The problem is with the update.
I have the following:
#InitBinder(value="deportistaActualizar")
public void actualizarInitBinder(WebDataBinder binder) {
logger.info(">>>>>>>> actualizarInitBinder >>>>>>>>>>>>>");
binder.setDisallowedFields("id");
}
Observe I have binder.setDisallowedFields("id")
public String crearActualizarFormulario(#PathVariable("id") String id, Model model){
logger.info("crearActualizarFormulario GET");
Deportista deportista = this.fakeMultipleRepository.findDeportista(id);
model.addAttribute("deportistaActualizar", deportista);
return "deportista.formulario.actualizacion";
}
#RequestMapping(value="/{id}/actualizar.htm", method=RequestMethod.POST)
public String actualizarPerson(#Validated #ModelAttribute("deportistaActualizar") Deportista deportista,
BindingResult result){
logger.info("actualizarPerson POST");
logger.info("{}", deportista.toString());
if(result.hasErrors()){
logger.error("There are errors!!!!");
for(ObjectError objectError : result.getAllErrors()){
logger.error("Error {}", objectError);
}
return "deportista.formulario.actualizacion";
}
logger.info("All fine!!!!");
this.fakeMultipleRepository.updateDeportista(deportista);
return "redirect:/manolo.htm";
}
The problem is:
when the form or command has any error, the controller re-render the view and the form appear showing the error messages how is expected, but without the ID value
or
if I try to update the object, of course keeping the id value, and without any error to simply proceed to update, it fails
The following appears in the Console:
- -------- createCollections ---------------
- >>>>>>>> actualizarInitBinder >>>>>>>>>>>>>
- Skipping URI variable 'id' since the request contains a bind value with the same name.
- actualizarPerson POST
- Deportista [id=null, nombre=Manuel, ...]
- There are errors!!!!
- Error Field error in object 'deportistaActualizar' on field 'id': rejected value [null]; codes [NotNull.deportistaActualizar.id,NotNull.id,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [deportistaActualizar.id,id]; arguments []; default message [id]]; default message [The field must be not empty]
The id is null. How I can around this problem keeping the Request Scope?
I have an alternate controller which is working with #SessionAttributes and all works perfect. But since is a huge risk if the user has many tabs open in the same web browser, one for create and other for updating, all is going to be very wrong. According with Spring MVC + Session attributes and multiple tabs, request scope should be used instead of session scope. It has sense.
Sadly seems Spring is not going to fix this:
#SessionAttributes doesn't work with tabbed browsing
Addition
According with your suggestion, I have the following:
#ModelAttribute("deportistaActualizar")
public Deportista populateActualizarFormulario(#RequestParam(defaultValue="") String id){
logger.info("populateActualizarFormulario - id: {}", id);
if(id.equals(""))
return null;
else
return this.fakeMultipleRepository.findDeportista(id);
}
Observe the method uses #RequestParam, my problem is how update that method to work when the URL to update has the following style
http://localhost:8080/spring-utility/deportista/1/actualizar.htm. There is no param in the URL, therefore #RequestParam is useless now.
I already have read the Spring Reference documentation:
Using #ModelAttribute on a method
Second Addition
Yes, you was right, and I did that yesterday, but I forget to share the following:
#ModelAttribute("deportistaActualizar")
public Deportista populateActualizarFormulario(#PathVariable(value="id") String id){
logger.info("populateActualizarFormulario - id: {}", id);
if(id.equals(""))
return null;
else
return this.fakeMultipleRepository.findDeportista(id);
}
Since a #ModelAttribute is called always before by any handler method, the following URL fails http://localhost:8080/spring-utility/deportista/registrar.htm, the following appears on the page
HTTP Status 400 -
type Status report
message
description The request sent by the client was syntactically incorrect.
Of course because the URL does not contains the expected id. Therefore I can't create new records to later edit/see.
I can confirm, that for the following work:
http://localhost:8080/spring-utility/deportista/1/detalle.htm
http://localhost:8080/spring-utility/deportista/1/actualizar.htm
the id (1) is retrieved.
How I could resolve this?
Thank You

Spring MVC optionally returning no view in a single mapping

I have a case where i need to :
Return a 304 not modified status if the blog hasnt been modified
Or return the blog view if it's been modified since If-Modified-Since request header
The problem is when i want to return 304 status, how do i tell spring mvc not to assume another view from the null return, and start sending the response with the status immediately ?
#RequestMapping(value={"/blogs/{blogId}"}, method=RequestMethod.GET)
public String hello(final HttpServletRequest req, final HttpServletResponse resp, final Model model,
#PathVariable("blogId") final String blogId) {
if (isModified(req, blogId)) {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return null; // this doesnt stop spring mvc to assume a view name
}
populate(model, grabBlog(blogId));
return "blog";
}
From the HTTP perspective returning a view doesn't make sense at all. The Spring documentation covers that use case:
#RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {
long lastModified = // 1. application-specific calculation
if (request.checkNotModified(lastModified)) {
// 2. shortcut exit - no further processing necessary
return null;
}
// 3. or otherwise further request processing, actually preparing content
model.addAttribute(...);
return "myViewName";
}
There are two key elements to note: calling request.checkNotModified(lastModified) and returning null. The former sets the response status to 304 before it returns true. The latter, in combination with the former, causes Spring MVC to do no further processing of the request.
You can create an exception with the appropriate annotation and then throw it. Spring will then generate a page for that error code.
From: http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order") // 404
public class OrderNotFoundException extends RuntimeException {
// ...
}

Error response in json format for Spring interceptor

I am writing a REST based web service. I need to return all the responses as JSON format. I have an interceptor to validate my authentication parameters. On authentication failure scenario, I have to return the error response in JSON format.
Currently i am doing
response.setHeader("Content-Type","application/json");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "{\"error\":\"Missing Authentication Parameters\"}");
The response body is coming as below.
JBoss Web/2.1.3.GA - Error report HTTP Status 401 - {"error":"Missing Authentication Parameters"}type Status reportmessage {"error":"Missing Authentication Parameters"}description This request requires HTTP authentication ({"error":"Missing Authentication Parameters"}).JBoss Web/2.1.3.GA
I need just the JSON string in response. Please help me.
You should probably be using spring-security for this. If you want to do it by hand, an alternative to using sendError on the response is to use spring MVC's #ExceptionHandler along with content negotiation to return JSON.
First define an error class*:
public class Error {
public message;
public exception;
public Error(String message, Exception ex) {
this.message = message;
this.exception = ex;
}
}
And an exception:
public class NotAuthenticatedException extends Exception {
// ...
}
Then in your controller you throw an exception at the appropriate time, catch it with #ExceptionHandler and return a ResponseEntity containing an Error instance and the appropriate error code.
#Controller
public class SimpleController {
#RequestMapping(...)
public String aMethod() {
// ...
throw new NotAuthenticatedException("Missing Authentication Parameters");
}
#ExceptionHandler(NotAuthenticatedException.class)
public ResponseEntity<Error> handleNotAuthenticatedException(
NotAuthenticatedException ex,
HttpServletRequest request) {
return new ResponseEntity<Error>(
new Error(ex.getMessage(), ex),
HttpStatus.UNAUTHORIZED
);
}
}
*use getters/setters to please the java convention gods

Resources