How to add a Rest validation in array property of an object? - java

I need to validate the request msisdn array type, I'm using rest validation of spring boot and how can I add the value in error message too.
I tried custom validation but I cannot find any array validation.
Desired error response
{
"errors": [
"Invalid msisdn: 0917854*****",
"Invalid msisdn: 0936895*****"
],
"success": false,
}
Request Body
{
"msisdn": ["0917854*****", "0936895*****"],
"message": "test message",
"title": "test title"
}
java object
public class PushNotif {
//validate this List
private List<String> msisdn;
#NotNull(message = "Message is required")
private String message;
private String title;
public PushNotif(List<String> msisdn, String message, String title) {
this.msisdn = msisdn;
this.message = message;
this.title = title;
}
}
java controller
#RestController
public class PushController extends BaseController{
#PostMapping(path = "/push")
public ResponseEntity<Object> indexAction(#valid #RequestBody PushNotif pushNotif){
return new ResponseEntity<Object>(null,HttpStatus.ok);
}
}
Error Response Handler
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler
{
#Override
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String, Object> errors = new HashMap<>();
List<String> details = new ArrayList<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
details.add(error.getDefaultMessage());
});
errors.put("details", details);
errors.put("success", false);
errors.put("traceid", "aksjdhkasjdhs-akjsdjksa-asjkdh");
return new ResponseEntity<>(errors, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
UPDATE: I added Parameter constraints to my java object
#NotNull(message = "msisdn is required")
private List<#NotNull(message = "msisdn is required")
#Pattern(regexp = "^09[0-9]{9}",
message = "Invalid msisdn ${validatedValue}") String> msisdn;

In order to validate values in a list, in this case values not null, you can add the #NotNull annotation in object reference type.
private List<#NotNull String> msisdn;
References
Hibernate Validations for Nested Container Elements

Related

Spring Boot #ControllerAdvice / #Valid

I am working on sample demo application for Exception Handling in Spring Boot.I am trying Exception Handling With #ControllerAdvice.
I would like to handle exception thrown by validator. It handles other exceptions but not MethodArgumentNotValidException.
For more details following are the classes I am working on:
Query.java
#Getter
#Setter
#NoArgsConstructor
#Validated
public class Query implements Serializable{
#Size(min = 7, max = 24, message = "Size must be between 7 and 24")
#Pattern(regexp = "[a-zA-Z0-9 ]+", Invalid characters")
private String number;
#Size(max = 2, message = "Size must be between 0 and 2")
#Pattern(regexp = "[a-zA-Z0-9 ]+", message="Invalid characters")
private String language;
}
ErrorResponse.java
#Setter
#Getter
#NoArgsConstructor
#JsonInclude(JsonInclude.Include.NON_NULL)
#Data
public class ErrorResponse
{
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime timestamp;
private HttpStatus status;
private int code;
private String error;
private String exception;
private String message;
private String path;
private List<String> errors;
}
CustomExceptionHandler.java
#SuppressWarnings({"unchecked","rawtypes"})
#ControllerAdvice
#Component("error")
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(NotFoundException.class)
public final ResponseEntity<Object> handleNotFoundError(NotFoundException ex, final HttpServletRequest request) {
ErrorResponse error = new ErrorResponse();
error.setTimestamp(LocalDateTime.now());
error.setMessage(ex.getMessage());
error.setCode(HttpStatus.NOT_FOUND.value());
return new ResponseEntity(error, HttpStatus.NOT_FOUND);
}
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(InternalServerException.class)
public final ResponseEntity<Object> handleInternelServorError(InternalServerException ex, final HttpServletRequest request) {
ErrorResponse error = new ErrorResponse();
error.setTimestamp(LocalDateTime.now());
error.setMessage(ex.getMessage());
error.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(ConstraintViolationException.class)
public void constraintViolationException(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value());
}
#ResponseStatus(HttpStatus.BAD_REQUEST)
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
List<String> errorList = ex
.getBindingResult()
.getFieldErrors()
.stream()
.map(fieldError -> fieldError.getDefaultMessage())
.collect(Collectors.toList());
ErrorResponse error = new ErrorResponse();
error.setCode(HttpStatus.BAD_REQUEST.value());
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}
}
Request
public ResponseEntity<?> getData(HttpServletRequest httpServletRequest,
#Valid #ApiParam(value = "MANDATORY. The number") #PathVariable(value = "number", required = true) final String partNumber,
#Valid #ApiParam(value = "OPTIONAL. The language") #RequestParam(value = "language", required = false) final String languageKey) {
.............
}
I just ran into this issue and here's how I solved it:
#ControllerAdvice
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// handle validation exception here
}
}
Note: if you have multiple classes that extend ResponseEntityExceptionHandler and are all #ControllerAdvice, you may have some trouble getting this overidden function to be executed. I had to have this overidden in a base class for all my exception handlers in order for it to finally be used. I may, in the future, put all my exception handlers into one class to avoid this.
source:
https://www.youtube.com/watch?v=Q0hwXOeMdUM
You create List<String> errorList but never use it and return just empty ErrorResponse

Java - Turn off Spring validation of the fields of a class in special cases

In my current implementation Validation for the two fields of SpecialTransactionDTO (transactionMetric and transactionRank) works in all cases. Now its parent class TransactionDTO that I receive as a #RequestBody contains a boolean field shouldValidate that indicates whether to validate the two fields of SpecialTransactionDTO or not.
How should I configure (turn off) validation for the cases when the value of shouldValidate flag is false?
#PostMapping("{id}/transaction/")
#ApiOperation(value = "Create transaction", httpMethod = "POST", response = TransactionDTO.class)
public ResponseEntity<Object> createTransaction(#PathVariable("id") Long accountId,
#Validated({TransactionNumber.class})
#RequestBody TransactionDTO transaction)
throws NotFoundException, URISyntaxException {
TransactionDTO result = transactionService.createTransaction(accountId, transaction);
return ResponseEntity.created(new URI("api/userAccount/" + accountId)).body(result);
}
#JsonSubTypes({
#JsonSubTypes.Type(value = SpecialTransactionDTO.class, name = SpecialTransactionDTO.TYPE),
#JsonSubTypes.Type(value = TransactionDTO.class, name = TransactionDTO.TYPE)
})
public class TransactionDTO {
#NotNull
private Long id;
#NotNull
private String transactionInitiator;
private Boolean shouldValidate;
private String transactionCode;
}
public class SpecialTransactionDTO extends TransactionDTO {
#NotNull
private Long userId;
#Pattern(regexp = "0|\\d{8,11}")
private String transactionMetric;
#Pattern(regexp = "\\d{1,3}")
private String transactionRank;
}
You could eliminate #Validated annotation altogether and imitate Spring's behaviour as follows:
#PostMapping("{id}/transaction/")
#ApiOperation(value = "Create transaction", httpMethod = "POST", response = TransactionDTO.class)
public ResponseEntity<Object> createTransaction(#PathVariable("id") Long accountId,
#RequestBody TransactionDTO transaction)
throws NotFoundException, URISyntaxException, MethodArgumentNotValidException {
// Check if we should validate here. Spring will convert your MethodArgumentNotValidException into HTTP 400
if(transaction.shouldValidate) {
SpringValidatorAdapter adapter = new SpringValidatorAdapter(validator);
BeanPropertyBindingResult result = new BeanPropertyBindingResult(transaction, transaction.getClass().getName());
adapter.validate(transaction, result, TransactionNumber.class);
if (result.hasErrors()) {
throw new MethodArgumentNotValidException(null, result);
}
}
TransactionDTO result = transactionService.createTransaction(accountId, transaction);
return ResponseEntity.created(new URI("api/userAccount/" + accountId)).body(result);
}

No suitable HttpMessageConverter found for response type and content type [application/json;charset=UTF-8] exception occurs

Im trying to hit Spring REST endpoint in my other module of the application. So im trying to use the REST Template to get a list of users as below :
The API request using REST Template :
public List<LeadUser> getUsersBySignUpType(String type, String id) {
String adminApiUrl = adminApiBaseUrl+"/crm/v1/users/?type="+type+"&id="+id;
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
HttpEntity entity = new HttpEntity(headers);
ResponseEntity<LeadUserList> response = restTemplate.exchange(
adminApiUrl, HttpMethod.GET, entity, LeadUserList.class);
return response.getBody().getUsersList();
}
LeadUserList class :
public class LeadUserList {
private List<LeadUser> usersList;
public List<LeadUser> getUsersList() {
return usersList;
}
}
LeadUser model class :
public class LeadUser {
#JsonProperty("id")
private String id;
#JsonProperty("email")
private String email;
#JsonProperty("name")
private String name;
#JsonProperty("businessName")
private String businessName;
#JsonProperty("phone")
private String phone;
#JsonProperty("address")
private String address;
#JsonProperty("createdTime")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Date createdTime;
#JsonProperty("updatedTime")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Date updatedTime;
#JsonProperty("bookletSignups")
private BookletSignUp bookletSignUp;
#JsonProperty("eventSignups")
private EventSignUp eventSignUp;
#JsonProperty("infoSignups")
private InfoSignUp infoSignUp;
#JsonProperty("webinarSignups")
private WebinarSignUp webinarSignUp;
public LeadUser() {
}
}
The API endpoint controller class :
#Controller
#Component
#RequestMapping(path = "/crm/v1")
public class UserController {
#Autowired
UserService userService;
#RequestMapping(value = "/users", method = GET,produces = "application/json")
#ResponseBody
public ResponseEntity<List<User>> getPartnersByDate(#RequestParam("type") String type,
#RequestParam("id") String id) throws ParseException {
List<User> usersList = userService.getUsersByType(type);
return new ResponseEntity<List<User>>(usersList, HttpStatus.OK);
}
}
Although the return type is JSON from the API endpoint im getting the above exception. What have I done wrong here?
The exception :
Could not extract response: no suitable HttpMessageConverter found for response type [class admin.client.domain.LeadUserList] and content type [application/json]
Try additional settings as follows,
httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
Also fix your exchange call,
ResponseEntity<List<LeadUser>> response = restTemplate.exchange(
adminApiUrl, HttpMethod.GET, entity, new ParameterizedTypeReference<List<LeadUser>>(){});

How to change object properties name when serialize?

I have an object like this.
public class Profile {
private String email;
private String phone;
#SerializedName("userCode")
private String user_code;
public String getEmail() {
return email;
}
public String getPhone() {
return phone;
}
public String getUser_code() {
return user_code;
}
}
This is what I got when I return that object in Rest API
{
"email": "abc#gmail.com",
"phone": 12345678,
"user_code": "742aeaefac"
}
Apparently annotation #SerializedName here did not work, I can understand that it get the object properties name base on its getter name, not in the annotation. If I change the getter name into getUserCode(), it will work as I expected.
I also try to use #JsonProperty but didn't help too.
Can someone explain what is the work here to solve this?
Update the code for serialization process in the controller.
#PostMapping(path = "/login", produces = "application/json")
#ResponseBody
public ClientRepresentation login(#RequestBody LoginRequest login) {
Map<String, Object> resObj = new HashMap<String, Object>();
ProfileResponse profileResponse = userService.findUserProfileByUserCode(login.getUserCode());
//Code logic to process object data...
resObj.put("profile", profileResponse);
return ClientRepresentationBuilder.buildClientRep(HttpStatus.OK.value(), "Success", resObj);
}
ClientRepresentation class
public class ClientRepresentation implements Serializable {
private Integer code;
private String message;
private Object data;
}

Jackson Field filter in Spring MVC

I have a spring boot application and I need to filter response body from RequestParam
Example :
// DTO
public class PersonDTO
{
private Long id;
private String firstName;
private String lastName;
}
// Controller
public class PersonController
{
#GetMapping(value = "/person")
public ResponseEntity<List<PersonDTO>> getPerson(#RequestParam(required = false) String filters)
{
List<PersonDTO> personList = myservoce.getPerson();
return new ResponseEntity<List<PersonDTO>>(personList, HttpStatus.OK);
}
}
Example of client query:
return all person without fields filter
http://localhost:8080/person
[
{
"id": 123,
"firstName": "toto1",
"lastName": "titi2"
},
{
"id": 345,
"firstName": "toto2",
"lastName": "titi2"
}
]
return all person and the response contain just firstName and lastName:
http://localhost:8080/person?filters=firstName,lastName
[
{
"firstName": "toto1",
"lastName": "titi2"
},
{
"firstName": "toto2",
"lastName": "titi2"
}
]
I have found this API "jackson-dynamic-filter", but the filter is used as annotation like this :
public class PersonController
{
#FilterOutAllExcept({"firstName", "lastName"})
#GetMapping(value = "/person")
public ResponseEntity<List<PersonDTO>> getPerson( #RequestParam(required = false) String filters )
{
List<PersonDTO> personList = myservoce.getPerson();
return new ResponseEntity<List<PersonDTO>>(personList, HttpStatus.OK);
}
}
in my case I cannot use this API because the list of field filter are managed by the client and it can be different for each call and my real payload Dto contain a lot of field
I have found also this API "jackson-antpathfilter" but it not work for me and also the response type is MappingJacksonValue and not a ResponseEntity>
Any idea how I can configure this use case with spring application ?
I have found temporary this solution :
#ControllerAdvice
public class JsonFilterAdvice implements ResponseBodyAdvice<List<?>>
{
#Override
public List<?> beforeBodyWrite(
List<?> arg0,
MethodParameter arg1,
MediaType arg2,
Class<? extends HttpMessageConverter<?>> arg3,
ServerHttpRequest arg4,
ServerHttpResponse arg5)
{
HttpServletRequest servletRequest = ((ServletServerHttpRequest) arg4).getServletRequest();
String[] params = servletRequest.getParameterValues("filters");
if (params != null)
{
// parse object and set field to null
}
return arg0;
}
#Override
public boolean supports(MethodParameter arg0, Class<? extends HttpMessageConverter<?>> arg1)
{
// return true if method parameters contain 'filters' field
return true;
}
any other suggestions are welcome
This is from my web service, you can use this code and adapt to your model and repository. From this you can create a generic service and call your modified version wherever you need it.
#RequestMapping(value = "/entidades/{id}/campos", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#ApiOperation(value = "Retrieves requested object fields", response = Entidade.class)
public ResponseEntity<Map<String, Object>> getFields(#Valid #PathVariable Long id, #RequestParam String campos) {
final Optional<Entidade> ent = entidadeRepository.findById(id);
final String[] camposArr = campos.split(",");
if (ent.isPresent()) {
final Entidade e = ent.get();
Map<String, Object> result = new HashMap<>();
for (String campo : camposArr) {
String methodName = "get" + (campo.substring(0, 1).toUpperCase() + campo.substring(1));
// System.out.println(campo);
try {
Method method = e.getClass().getMethod(methodName);
// System.out.println(method.invoke(e));
result.put(campo, method.invoke(e));
} catch (Exception err) {
}
}
return new ResponseEntity<>(result, HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
You can add annotation #JsonIgnore of Jackson lib to filter field id:
public class PersonDTO implements Serializable
{
#JsonIgnore
private Long id;
private String firstName;
private String lastName;
}

Categories