Deserializing interfaces with Rest controller in Java Spring framework - java

I have inherited a simple enough controller. The problem is that it returns interface and Spring AFAIK cannot deserialize if it can't instantiate. I can fix it with one annotation on (each) transport interface, but it removes any benefit of returning an interface as opposed to a concrete class and steps out of bounds of DTO responsibilities.
Therefore my question is can something be done on the client side to consume controller as it is? If not, can something be done on controller code without altering UserDto interface?
Here's the Rest controller:
#RestController
#RequestMapping(value = "/user")
public class UserController {
// ...
#RequestMapping(value = "/create", method = RequestMethod.POST)
public UserDto create(#RequestBody UserDto user) throws ServiceException {
return service.save(user);
}
// ...
}
Here's the test that acts as a client and consumes the controller:
#Test
public void UserControllerRealTest() {
ClientUser user1 = new ClientUserImpl("ADSMarko", "Marko Savić", "Slayer!!!");
RestTemplate rt = new RestTemplate();
HttpEntity<ClientUser> request = new HttpEntity<>(user1);
UserDto result1 = rt.postForObject(createUrl, request, UserDtoImpl.class);
System.out.println("User " + result1.getUsername() + " saved with id " + result1.getId());
ClientUser user2 = new ClientUserImpl("Marko", "Marko Savić", "Slayer!!!");
request = new HttpEntity<>(user2);
ParameterizedTypeReference<UserDto> ptr = new ParameterizedTypeReference<UserDto>() {};
ResponseEntity<UserDto> re = rt.exchange(createUrl, HttpMethod.POST, request, ptr);
UserDto result2 = re.getBody();
System.out.println("User " + result2.getUsername() + " saved with id " + result2.getId());
}
Bellow is the interface in question. It works with the leading annotation, but without it throws com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
#JsonDeserialize(as = UserDtoImpl.class) // Or the longer annotation
public interface UserDto {
// ... just getters and setters
}

Related

Access BindingResult of Spring WebDataBinder without putting in the argument list

I am trying to use Spring validation with a controller interface generated by swagger-codegen. The swagger code generation supplies an abstract class for a controller. Our controller implements the codegen class and provides the actual logic. I would like to access the BindingResult in my controller methods, but swagger-codegen does not generate that parameter in its interface. Is there any way to get ahold of the BindingResults object other than specifying it as a parameter?
To make this more concrete, the codegen makes the endpoint like this (noisy code removed):
#RequestMapping(value = "/api/repository/v1/datasets",
produces = { "application/json" },
consumes = { "application/json" },
method = RequestMethod.POST)
default ResponseEntity<JobModel> createDataset(#Valid #RequestBody DatasetRequestModel dataset) {
...
}
We implement a controller with the usual binder setup like:
#InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.addValidators(requestValidator)
}
but within the endpoint, we have no way to get the BindingResult since it has to match the signature of the codegen entry:
public ResponseEntity<StudySummaryModel> createStudy(#Valid #RequestBody StudyRequestModel studyRequest) {
...
}
I think the most straightforward solution may be to skip using WebDataBinder. Instead, I can have each controller endpoint call validators directly.
I found another approach besides hand coding the validation; using an #ControllerAdvice class that extends ResponseEntityExceptionHandler.
There is a nice example here: Spring Validation Example
Here is my code based on that example that formats the error:
#ControllerAdvice
public class ApiValidationExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request
) {
BindingResult bindingResult = ex.getBindingResult();
List<String> errorDetails = bindingResult
.getFieldErrors()
.stream()
.map(err -> err.getCode() + " error on '" + err.getObjectName() + "': " + err.getDefaultMessage())
.collect(toList());
ErrorModel errorModel = new ErrorModel()
.message("Validation errors - see error details")
.errorDetail(errorDetails);
return new ResponseEntity<>(errorModel, HttpStatus.BAD_REQUEST);
}
}

Prototype bean inside #Controller not working as expected

I have a controller that contains two methods. First one generates a random captcha value, and second one compares that and the input the user wrote. The problem was when multiple users tried to validate the captcha value, the last generated value was validated correctly to preview generated values for other users.
#Controller
#RequestMapping
public class CaptchaController {
private Producer captchaProducer = null;
#Autowired
private DataCaptcha dataCaptcha;
#Autowired
public void setCaptchaProducer(Producer captchaProducer) {
this.captchaProducer = captchaProducer;
}
#RequestMapping(value = "/generate-captcha.json", method = RequestMethod.GET, produces = "application/json")
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
String captchaText = captchaProducer.createText();
dataCaptcha.setCaptcha(captchaText);
dataCaptcha.setPasoCaptcha(false);
System.out.println("$$$$$$$$$$$$$$$$ "+ dataCaptcha.getCaptcha()); // output: null
BufferedImage bi = captchaProducer.createImage(captchaText);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(bi, "jpg", out);
out.flush();
out.close();
return null;
}
#RequestMapping(value = "/validate-captcha.json", method = RequestMethod.POST, produces = "application/json")
public #ResponseBody Map<String, Object> validarCaptcha(HttpServletRequest request,
#RequestParam(value = "valueCaptcha", defaultValue = "") String valueCaptcha) {
String captchaId = dataCaptcha.getCaptcha();
Boolean rpta = StringUtils.equalsIgnoreCase(captchaId, valueCaptcha);
String message = "";
String messageType = "OK";
Map<String, Object> response = new HashMap<String,Object>();
if (!rpta) {
message = "incorrect captcha";
messageType = "ERROR";
dataCaptcha.setPasoCaptcha(false);
} else {
dataCaptcha.setPasoCaptcha(true);
}
response.put("messageType", messageType);
response.put("message", message);
response.put("object", rpta);
return response;
}
}
That error was due to #Controller bean singleton and I needed a Prototype scope in my bean. So I tried different ways to do this:
Making the controller webApplicationContext-aware, as described here
Using #Lookup, example here
Finally tried Scope Proxy as described here
DataCaptcha:
import lombok.Getter;
import lombok.Setter;
#Component
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
#Getter
#Setter
public class DataCaptcha {
private String captcha;
private boolean pasoCaptcha;
}
Non of them worked. Tried debugging and in this particular line on the controller
String captchaText = captchaProducer.createText();
dataCaptcha.setCaptcha(captchaText);
captchaText has a value, but after using setCaptcha and checking the object dataCaptcha, the captcha field is null.
I'm using Spring Boot 2.0.3
I think your DataCaptcha variable should not be a class variable as your have it in the controller, but instead be a variable inside your handleRequest method.
Then, using the spring context (as you hinted at), perform a
DataCaptcha dataCaptcha = ctx.getBean(DataCaptcha.class)
to get a prototyped instance.
Singleton Bean holds the reference to same prototype bean throughout its lifetime. You are right, you need to have the DataCaptcha in prototype scope or else it would be shared between threadContexts and multiple users would be able to use the same captcha. But also, You need to have it inside method and cannot have it at class level, because if its class level, there would be just one object created of controller class (singleton bean) and its associated DataCaptcha would be same.
As you already pointed, webApplicationContext-aware, you need to get a new local instance of DataCaptcha inside your methods. You can try to have a unique ID added to DataCaptcha and retrieve the captcha using the ID for validation purpose. Or you may put it in userSession object on server side, and clear it off once it is validated successfully.

how to properly handle POST in a custom Spring Data Rest controller?

in my Spring Data Rest application I have a standard repository:
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
List<Person> findByLastName(#Param("name") String name);
}
I also have a custom controller, which will implement some additional logic upon HTTP POST:
#RestController
#RequestMapping("/people")
public class PersonController {
#RequestMapping(value = "/**", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> savePerson(#RequestBody Person person, UriComponentsBuilder b, #RequestParam Map<String, ?> id) {
UriComponents uriComponents =
b.path("/people/").buildAndExpand();
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setLocation(uriComponents.toUri());
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>("Hello World\n\n", responseHeaders, HttpStatus.CREATED);
}
}
What is the proper way to save my "Person" entity within this controller, since I'm not using Hibernate Entity Manager explicitly?
The "person" parameter is just a POJO, so it does not have any persistance CRUD methods.
If the Person class used in the PersonRepository is same as whatever you are using in the controller to map RequestBody to, then in the controller method you can just do personRepository.save(person) -- Assuming personRepository is an Autowired instance of PersonRepository class.
I am guessing that, you are experimenting with spring data rest https://spring.io/guides/gs/accessing-data-rest/ . If that's the case, you might have in-memory database com.h2database:h2 in your class path. Thats why, in the given example, everything is just working without you even configuring the database or adding any JPA annotations to your person class. So, you can still do personRepository.save(person) from your custom controller without having any of the JPA annotations in your Person class.

Multiple Spring MVC validator for the same Controller

I am having a Spring controller with a Validator defined as:
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new MyValidator(myService));
}
And calling it:
public ResponseEntity<?> executeSomething(
#ApiParam(name = "monitorRequest", required = true, value = "") #Valid #RequestBody MonitorRequest monitorRequest,
HttpServletRequest request, HttpServletResponse response) throws RESTException
I need to add one more Validator for this controller that could be called from some specific methods of this controller. Is there any way to achieve this?
EDIT: I am handling the Error by:
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseBody
public ResponseEntity<?> processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
ValidationErrorObj obj = processFieldErrors(fieldErrors);
ResponseEntity r = new ResponseEntity(obj, HttpStatus.BAD_REQUEST);
return r;
}
You can have more than one InitBinder method in a controller. It is controlled by the optional value parameter . For the javadoc of InitBinder : String[] value : The names of command/form attributes and/or request parameters that this init-binder method is supposed to apply to ... Specifying model attribute names or request parameter names here restricts the init-binder method to those specific attributes/parameters, with different init-binder methods typically applying to different groups of attributes or parameters.
Another way would be to explicely call a complementary Validator in specific methods.
BTW : I can't see any Errors or BindingResult in your controller method signature : where do you find whether errors occured ?
For those who are still trying to figure out how to solve this in 2017. I was facing similar issues while trying to implement 2 validators in my RestController. I followed the approach mentioned above by #Serge Ballasta.
I ended up making 2 Model each of linked to their specific Validators. The Controller methods look something like
#RequestMapping(value = "register", method = RequestMethod.POST)
public ResponseEntity<User> register(#Valid #RequestBody UserRegisterRequest userRegisterRequest) {
return null;
}
#RequestMapping(value = "test", method = RequestMethod.POST)
public ResponseEntity<?> test(#Valid #RequestBody TestRequest testRequest) {
return null;
}
and I created 2 initBinders to wire these validators in the controller like
#InitBinder("testRequest")
public void setupBinder(WebDataBinder binder) {
binder.addValidators(testValidator);
}
#InitBinder("userRegisterRequest")
public void setupBinder1(WebDataBinder binder) {
binder.addValidators(userRegistrationRequestValidator);
}
Please note that the #RequestBody attributes (userRegisterRequest , testRequest) had to be provided as values in the #InitBinder() annotations.
By the way the in my code I handle the bindingResult in a custom ExceptionHandler class which extends ResponseEntityExceptionHandler which gives me freedom to do custom handling of the response.

Spring Bean Validation #Valid handling

I have created a Spring MVC REST service using Bean Validation 1.2 with the following method:
#RequestMapping(value = "/valid")
public String validatedMethod(#Valid ValidObject object) {
}
If object isn't valid, Tomcat informs me that The request sent by the client was syntactically incorrect. and my validatedMethod is never called.
How can I get the message that was defined in the ValidObject bean? Should I use some filter or interceptor?
I know that I can rewrite like below, to get the set of ConstraintViolations from the injected Validator, but the above seems more neat...
#RequestMapping(value = "/valid")
public String validatedMethod(ValidObject object) {
Set<ConstraintViolation<ValidObject>> constraintViolations = validator
.validate(object);
if (constraintViolations.isEmpty()) {
return "valid";
} else {
final StringBuilder message = new StringBuilder();
constraintViolations.forEach((action) -> {
message.append(action.getPropertyPath());
message.append(": ");
message.append(action.getMessage());
});
return message.toString();
}
}
I believe a better way of doing this is using ExceptionHandler.
In your Controller you can write ExceptionHandler to handle different exceptions. Below is the code for the same:
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationFailureResponse validationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
final List<FieldError> fieldErrors = result.getFieldErrors();
return new ValidationFailureResponse((FieldError[])(fieldErrors.toArray(new FieldError[fieldErrors.size()])));
}
When you send a bad request to the Controller, the validator throws an exception of type MethodArgumentNotValidException. So the ideal way would be to write an exception handler to specifically handle this exception.
There you can create a beautiful response to tell the user of things which went wrong.
I advocate this, because you have to write this just once and many Controller methods can use it. :)
UPDATE
When you use the #Valid annotation for a method argument in the Controller, the validator is invoked automatically and it tries to validate the object, if the object is invalid, it throws MethodArgumentNotValidException.
If Spring finds an ExceptionHandler method for this exception it will execute the code inside this method.
You just need to make sure that the method above is present in your Controller.
Now there is another case when you have multiple Controllers where you want to validate the method arguments. In this case I suggest you to create a ExceptionResolver class and put this method there. Make your Controllers extend this class and your job is done.
Try this
#RequestMapping(value = "/valid")
public String validatedMethod(#Valid ValidObject object, BindingResult result) {
StringBuilder builder = new StringBuilder();
List<FieldError> errors = result.getFieldErrors();
for (FieldError error : errors ) {
builder.append(error.getField() + " : " + error.getDefaultMessage());
}
return builder.toString();
}
When you use #Valid and doing bad request body Spring handle MethodArgumentNotValidException
You must create special class and extend ResponseEntityExceptionHandler and override handleMethodArgumentNotValid
Example
#ControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(UserExistException.class)
public ResponseEntity<Object> handleUserExistException(
UserExistException e, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.BAD_REQUEST.value());
body.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
body.put("message", e.getMessage());
body.put("path", request.getDescription(false).replace("uri=", ""));
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.BAD_REQUEST.value());
body.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
body.put("path", request.getDescription(false).replace("uri=", ""));
return new ResponseEntity<>(body, headers, status);
}
}
The answer by #dharam works.
For users at Spring v4.3, Here's a nice implementation which uses a Custom Exception class to handle exception by type.
#RestControllerAdvice
public class CustomExceptionClass extends ResponseEntityExceptionHandler{
#ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleException(MethodArgumentNotValidException ex, WebRequest req){
// Build your custom response object and access the exception message using ex.getMessage()
}
}
This method will enable handling all #Valid exceptions across all of your #Controller methods in a consolidated way

Categories