How to validate Spring-Boot mapped Entitys - java

Iam trying to validate the following Scheme with the Spring Validator:
#RestController
#RequestMapping("/bankcode-service")
#Validated
public class BankcodeController {
#Autowired
Delegator delegator;
#Autowired
Delegator delegator;
#Autowired
BankcodeRepository bankcodeRepository;
#DeleteMapping(path = "/bankcode", consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public DeferredResult<ResponseEntity<String>> lock(#Valid HttpEntity<BankcodeJSONEntity> httpEntity) {
DeferredResult<ResponseEntity<String>> response = new DeferredResult<>();
if (httpEntity.getBody() == null) {
response.setResult(new ResponseEntity<>("The request was empty!", HttpStatus.BAD_REQUEST));
return response;
}
response.setResult(delegator.delegateUseCase(new LockBankcodeProd(bankcodeRepository, httpEntity.getBody())));
return response;
}
The DTO used looks like that:
#Data
public class BankcodeJSONEntity {
#NotNull
#Size(min = 8, max = 8)
private String bankcode;
#NotNull
#Size(min = 11, max = 11)
private String bic;
#NotNull
private String ticket;
#Basic
#Temporal(TemporalType.DATE)
#NotNull
private Date date;
#NotNull
private String category;
#NotNull
private String name;
}
But no matter if I pass in:
{"bankcode":"00000000", "bic":"AAAAAAAAAAA", "ticket":"SPOC-000000", "date":"2020-01-17", "category":"Fusion", "name":"Fantasiebank"}
Or an invalid one:
{"bankcode":"21750000", "bic":"AAAAAAAA", "ticket":"SPOC-000000", "date":"2020-01-17", "category":"Fusion", "name":"Fantasiebank"}
There is no constraintvalidationexception thrown. In many Tutorials I've seen that the validation is mostly done with concrete Arguments instead of a DTO. Is the DTO Validation possible because I can only have 7 Constructor Arguments before SonarLint lowers my Code Quality.
What am I doing wrong here?

Please remove HttpEntity from parameter. Change your parameter as #Valid BankcodeJSONEntity entity.
Because HttpEntity represents HTTP request or response including headers and body, usually used with RestTemplate. And for controller, usually as response wrapper.
public DeferredResult<ResponseEntity<String>> lock(#Valid BankcodeJSONEntityentity) {

Related

How to programatically trigger javax validations in a POJO class

I have a spring boot project in which I'm facing a data validation issue while doing a post request.
Problem
While doing a post request I'm mapping the request body to a POJO which does not have any javax validations in itself, but the class has fields of another two POJOs which have data validations in them. How can I trigger data validations in the inner POJOs programmatically and throw relevant exceptions if required. I'm using Spring boot v2.5.2.
In the post request I have:
#PostMapping("/signup")
public ResponseEntity<String> addNewUser(#RequestBody #Valid NewUserDetailsPojo newUserDetailsPojo) {
log.debug("Adding new User: {}", newUserDetailsPojo);
Integer userId = userService.addNewUser(newUserDetailsPojo);
if (userId == null) {
return ResponseEntity.badRequest().build();
} else {
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(userId).toUri();
return ResponseEntity.created(location).build();
}
}
Where NewUserDetailsPojo is a simple POJO class having structure:
public class NewUserDetailsPojo {
private BasicDetailsPojo basicDetailsPojo;
private DoctorPojo doctorPojo;
// constructor
public NewUserDetailsPojo(BasicDetailsPojo basicDetailsPojo, DoctorPojo doctorPojo) {
/** before mapping the incoming data to the fields, I want to validate the data
* with my predefined javax.validations constraints declared in the respective
* classes
*/
this.basicDetailsPojo = basicDetailsPojo;
this.doctorPojo = doctorPojo;
}
}
So, as I mentioned earlier, NewUserDetailsPojo does not have any data validations in itself, but its two fields which are of the class
BasicDetailsPojo
DoctorPojo
have data validations in them. I want to invoke javax validations in the constructor of NewUserDetailsPojo and throw suitable exceptions if nessecery.
I'm giving the structures of BasicDetailsPojo and DoctorPojo below:
public class BasicDetailsPojo {
#Size(min = 5, message = "Name should be at least 5 characters long")
private String name;
#Email
private String email;
#Size(min = 10, message = "Contact number must be of 10 digits")
private String contactNo;
private String role;
#NotNull
#Size(min = 8, message = "Password must be 8 characters long")
private String password;
}
This is the structure of DoctorPojo:
public class DoctorPojo extends BasicDetailsPojo {
#NotNull
private String regNo;
#NotNull
private String degree;
#NotNull
private String specialization;
private String experience;
}
I think your problem will be fixed by putting #Valid annotation in the NewUserDetailsPojo class over the basicDetailsPojo and doctorPojo atributes
https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/?v=5.3#section-object-graph-validation

Facing issue on service side validation

I'm trying to implement the server side validation using spring. but its not validating. Here is my code sample.
#RestController
#RequestMapping("/api/v1/note")
public class NoteController {
#Autowired
private final NoteService noteService;
#PostMapping
public ResponseEntity<String> create(#Valid #RequestBody final NoteDto noteDto){
noteService.create(noteDto);
return new ResponseEntity<>("sucess", HttpStatus.CREATED);
}
}
POJO..
#Data
#JsonInclude(value = Include.NON_NULL)
public class NoteDto {
#NotEmpty(message = "Building No can't be empty!")
private String buildingNo;
private String buildingName;
#NotEmpty(message = "Street can't be empty!")
}
What am missing here
#Valid annotation that triggers validations on the NoteDto (in this case #NotNull and #Future). These annotations could come from different JSR-303 providers (e.g, Hibernate, Spring..etc).
Example
static class NoteDto {
#NotNull #Future
private Date date;
}
And Remove final.

Trouble reading JSON from URL to java objects / storing in mysql db

I'm a newbie coder having just finished a 6 month coding crash-course. I'm working on a java webapp to demonstrate my skills, and the project idea I had involves retrieving JSON data from an API, something we didn't learn about in class. I made POJOs to match the JSON, and I'm trying to parse the JSON into java objects to store in a database, however my database tables are never filled with data when I run through the app. I suspect the problem is somewhere with my method to convert the JSON but any feedback is greatly appreciated. Here's all my code I think is relevant, sorry if its TMI. I also apologize if my code is ugly, I'm a beginner... Thanks!
API returns JSON like this:
{
"result":{
"status":1,
"num_results":1,
"total_results":500,
"results_remaining":499,
"matches":[{
"match_id":3188095188,
"match_seq_num":2784956606,
"start_time":1495079320,
"lobby_type":7,
"radiant_team_id":0,
"dire_team_id":0,
"players":[{
"account_id":86920222,
"player_slot":0,
"hero_id":18
},{
"account_id":61122568,
"player_slot":1,
"hero_id":85
},{
"account_id":10208661,
"player_slot":2,
"hero_id":13
},{
"account_id":106083675,
"player_slot":132,
"hero_id":50
}]
}]
}
}
My POJOs:
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Result {
#JsonIgnore
#Id
#GeneratedValue
private int id;
#JsonProperty("status")
private int status;
#JsonProperty("num_results")
private int num_results;
#JsonProperty("total_results")
private int total_results;
#JsonProperty("results_remaining")
private int results_remaining;
#OneToMany
#JoinColumn(name = "result_id")
#ElementCollection(targetClass=Matches.class)
#JsonProperty("matches")
private List<Matches> matches;
// getters and setters
}
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Matches {
#Id
#JsonProperty("match_id")
private int match_id;
#JsonIgnore
#ManyToOne
private Result result;
#JsonProperty("match_seq_num")
private int match_seq_num;
#JsonProperty("start_time")
private int start_time;
#JsonProperty("lobby_type")
private int lobby_type;
#JsonProperty("radiant_team_id")
private int radiant_team_id;
#JsonProperty("dire_team_id")
private int dire_team_id;
#OneToMany
#JoinColumn(name = "Matches_id")
#ElementCollection(targetClass=Players.class)
#JsonProperty("players")
private List<Players> players;
// getters and setters
}
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Players {
#JsonIgnore
#Id
#GeneratedValue
private int id;
#JsonIgnore
#ManyToOne
private Matches matches;
#JsonProperty("account_id")
private int account_id;
#JsonProperty("player_slot")
private int player_slot;
#JsonProperty("hero_id")
private int hero_id;
// getters and setters
}
Services method to read and convert the JSON to objects (url is censored, don't want my API key to be public)
public class SteamService {
public static Result getMatchHistory(String steamid){
Result result = new Result();
String MatchHistoryUrl = "https:**URL**="+steamid;
RestTemplate restTemplate = new RestTemplate();
Result jsonresult = restTemplate.getForObject(MatchHistoryUrl, Result.class);
return jsonresult;
}
}
Controller
#Controller
#RequestMapping("")
public class HomeController {
#Autowired
private ResultsDao resultsDao;
#RequestMapping(value = "", method = RequestMethod.GET)
public String index(Model model){
model.addAttribute("title", "Welcome");
return "home/home";
}
#RequestMapping(value = "", method = RequestMethod.POST)
public String processSteamIdField(#RequestParam("steamid")String steamid, Model model) {
Result newresult = getMatchHistory(steamid);
resultsDao.save(newresult);
return "redirect:results";
}
}
DAO
#Repository
#Transactional
public interface ResultsDao extends CrudRepository<Result, Integer>{
}
Maybe my approach is a bit naive, but... If you want to store the JSON as string in the database, then I would use an object mapper for this:
new ObjectMapper().writeValueAsString(myObject);
and for reading a JSON and parsing it to a class I would do:
new ObjectMapper().readValue(JSON_STRING_HERE, "utf-8"), MyPOJO.class);
Also, if you already are using Spring, then your controller may look like this (for a POST, for example)
#RequestMapping(value = "/", method = RequestMethod.POST)
public MyPojo myController(#RequestBody MyPojo myBody) {
myRepository.save(myBody);
}
So, the parsing of the JSON that the client is sending to your app and your controller is already handled by Spring

How to return 400 HTTP error code when some property of a RequestBody parameter is null?

I have the following example:
This is the request body:
public class UserLoginData
implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String password;
//... getter and setters
}
This is the Controller:
#RequestMapping(value = {"/login"}, method = RequestMethod.POST)
#ResponseBody
public LoginResponse login(#RequestBody(required = true) UserLoginData loginData){
//... some code
}
This is how I invoke the service:
POST /login
{"username":"neuquino"}
I expect that Spring returns a HTTP 400 BAD REQUEST error, because password is missing. But instead of that, it returns a HTTP 500 INTERNAL SERVER error with the following stacktrace:
java.lang.NullPointerException
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:948) ~[spring-webmvc-3.2.2.RELEASE.jar:3.2.2.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838) ~[spring-webmvc-3.2.2.RELEASE.jar:3.2.2.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:755)
...
How can I specify to Spring that username and password are required fields in request body?
#Bart's answer was very useful to find my final solution:
public class UserLoginData
implements Serializable {
private static final long serialVersionUID = 1L;
#NotNull
#NotBlank
private String username;
#NotNull
#NotBlank
private String password;
//... getter and setters
}
On my Controller I have:
public LoginResponse login(
#RequestBody(required = true) #Valid UserLoginData loginData){
//... login code
}
Until here is very similar, but it is clearer because the controller's method does not have the error validation. Instead of that, I used another class with the ControllerAdvice annotation
#ControllerAdvice
public class RestErrorHandler {
private MessageSource messageSource;
#Autowired
public RestErrorHandler(#Qualifier("messageSource") MessageSource messageSource) {
this.messageSource = messageSource;
}
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationError processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
return this.processFieldErrors(fieldErrors);
}
private ValidationError processFieldErrors(List<FieldError> fieldErrors) {
ValidationError dto = new ValidationError();
for (FieldError fieldError : fieldErrors) {
String localizedErrorMessage = this.resolveLocalizedErrorMessage(fieldError);
dto.getErrors().put(fieldError.getField(), localizedErrorMessage);
}
return dto;
}
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = this.messageSource.getMessage(fieldError, currentLocale);
return localizedErrorMessage;
}
}
Now my service is returning this:
{
"errors":{
"country":"country cannot be null"
}
}
I hope it helps someone else.
To get this solution I also used what is written in this post.
If the password is missing it will not be set when the UserLoginData object is created. It will not check if the value is valid or anything. If you need to validate your login data use proper validation.
You could use the annotations in the hibernate validator package for declarative validation e.g.
public class UserLoginData
implements Serializable {
private static final long serialVersionUID = 1L;
#NotNull
#NotBlank
private String username;
#NotNull
#NotBlank
private String password;
//... getter and setters
}
Your method could then be written as (note the #Valid annotation):
public LoginResponse login(
#RequestBody(required = true) #Valid UserLoginData loginData,
BindingResult result,
HttpServletResponse response){
if (result.hasErrors()) {
// Validation problems!
response.sendError(400, "Bad login data");
}
}

How to validate two or more beans in a Spring Controller method with Hibernate Validator (JSR 303)

I have two classes (Beans)
public class BeanOne {
#Min(1)
private Integer idBeanOne;
#NotBlank
private String nameBeanOne;
#NotNull
#Min(1)
private Integer idOther;
// ... Getters and Setters
}
public class BeanTwo {
#Min(1)
private Integer idBeanTwo;
#NotBlank
private String nameBeanTwo;
#NotNull
#Min(1)
private Integer idOtherTwo;
// ... Getters and Setters
}
Controller of Spring
// Method in Controller
#RequestMapping(value = "/name.html", method = RequestMethod.POST)
public #ResponseBody
Map<String, Object> submitInsert(#Valid BeanOne one,
#Valid BeanTwo two, BindingResult result) {
if (result.hasErrors()) {
// Errores
} else {
// :D
}
}
Is there any way that I can validate two or more beans? I have successfully validated a single bean, but I have not been successful in validating two or more beans. How can I do this?
thanks: D
thanks: D
After many attempts to validate two or more beans with JSR303, come to this solution.
public class BeanOne {
#Valid
private BeanTwo beanTwo;
// other beans to validate
#Valid
private BeanN beanN;
#Min(1)
private Integer idBeanOne;
#NotBlank
private String nameBeanOne;
#NotNull
#Min(1)
private Integer idOther;
// ... Getters and Setters
}
public class BeanTwo {
#Min(1)
private Integer idBeanTwo;
#NotBlank
private String nameBeanTwo;
#NotNull
#Min(1)
private Integer idOtherTwo;
// ... Getters and Setters
}
// Controller Spring
#Controller
public class XController {
#Autowired
private Validator validator;
#RequestMapping(value = "/name.html", method = RequestMethod.POST)
public #ResponseBody Map<String, Object>
submitInsert(BeanOne beanOne, BeanTwo beanTwo, BindingResult result) {
beanOne.setBeanTwo(beanTwo);
// beanOne.setBeabN(beanN);
validator.validate(beanOne, result);
if (result.hasErrors()) {
// Errores
} else {
// :D
}
}
// more code ...
}
But now I have another problem :(
I have this file Messages.properties
typeMismatch.java.lang.Integer = Must specify an integer value.
typeMismatch.java.lang.Long = Must specify an integer value.
typeMismatch.java.lang.Float = Must specify a decimal value.
typeMismatch.java.lang.Double=Must specify a decimal value.
This file helps me to catch exceptions, when a field expects a number, and the user enters text
This works perfectly for the first bean (BeanOne) but not for nested beans (BeanTwo, BeanN)
I hope they can help me: D
thanks

Categories

Resources