Java: GroupSequenceProvider for Validation, object is null in getValidationGroups method - spring

This is what I am trying to achieve:
I have an update request object and user is allowed to do Partial Updates. But I want to validate the field only if it is in the request body. Otherwise, it is OK to be null. To achieve this, I am using GroupSequenceProvider to let the Validator know what groups to validate. What am I doing wrong here? If there is a blunder, how do I fix it?
Documentation: https://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/chapter-groups.html#example-implementing-using-default-group-sequence-provider
#GroupSequenceProvider(UpdateUserRegistrationGroupSequenceProvider.class)
public class UpdateUserRegistrationRequestV1 {
#NotBlank(groups = {EmailExistsInRequest.class})
#Email(groups = {EmailExistsInRequest.class})
#SafeHtml(whitelistType = SafeHtml.WhiteListType.NONE, groups = {EmailExistsInRequest.class})
private String email;
#NotNull(groups = {PasswordExistsInRequest.class})
#Size(min = 8, max = 255, groups = {PasswordExistsInRequest.class})
private String password;
#NotNull(groups = {FirstNameExistsInRequest.class})
#Size(max = 255, groups = {FirstNameExistsInRequest.class})
#SafeHtml(whitelistType = SafeHtml.WhiteListType.NONE, groups = {FirstNameExistsInRequest.class})
private String firstName;
// THERE ARE GETTERS AND SETTERS BELOW
}
Group Sequence Provider Code:
public class UpdateUserRegistrationGroupSequenceProvider implements DefaultGroupSequenceProvider<UpdateUserRegistrationRequestV1> {
public interface EmailExistsInRequest {}
public interface PasswordExistsInRequest {}
public interface FirstNameExistsInRequest {}
#Override
public List<Class<?>> getValidationGroups(UpdateUserRegistrationRequestV1 updateUserRegistrationRequestV1) {
List<Class<?>> defaultGroupSequence = new ArrayList<Class<?>>();
defaultGroupSequence.add(Default.class);
defaultGroupSequence.add(UpdateUserRegistrationRequestV1.class);
if(StringUtils.hasText(updateUserRegistrationRequestV1.getEmail())) {
defaultGroupSequence.add(EmailExistsInRequest.class);
}
if(StringUtils.hasText(updateUserRegistrationRequestV1.getPassword())) {
defaultGroupSequence.add(PasswordExistsInRequest.class);
}
if(StringUtils.hasText(updateUserRegistrationRequestV1.getFirstName())) {
defaultGroupSequence.add(FirstNameExistsInRequest.class);
}
return defaultGroupSequence;
}
}
I am using Spring MVC, so this is how my controller method looks,
#RequestMapping(value = "/{userId}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.NO_CONTENT)
public void updateUser(#PathVariable("userId") Long userId,
#RequestBody #Valid UpdateUserRegistrationRequestV1 request) {
logger.info("Received update request = " + request + " for userId = " + userId);
registrationService.updateUser(userId, conversionService.convert(request, User.class));
}
Now the problem is, the parameter "updateUserRegistrationRequestV1" in the UpdateUserRegistrationGroupSequenceProvider.getValidationGroups method is null. This is the request object that I am sending in the request body and I am sending email field with it.
What am I doing wrong?

I too went through the same issue ,and hopefully solved it
You just have to check the object is null and put all your conditions inside it.
public List<Class<?>> getValidationGroups(Employee object) {
List<Class<?>> sequence = new ArrayList<>();
//first check if the object is null
if(object != null ){
if (!object.isDraft()) {
sequence.add(Second.class);
}
}
// Apply all validation rules from default group
sequence.add(Employee.class);
return sequence;
}

Related

Spring boot Restful API: DTO with relationships convert to entity using ModelMapper?

I'm now confused about how to do a CRUD in a Rest API with Spring.
Let me explain, I have two routes to POST and PUT an entity. I created two DTOs createPostRequest and updatePostRequest for this. Because when adding, the properties cannot be null, while when updating they can (nulled properties are ignored).
Problem 1:
On my frontend, the user is asked to choose a list of tags from the database (multi select html). This is why createPostRequest has a tags property typed TagDTO. But, how can I use modelMapper to map, for example, the createPostRequest to the Post entity making sure that the tags exist in the database?
if for example a user try to insert a tag that does not exist, I was thinking of doing something like this:
postEntity.setTags(tagService.findAllByIds(postEntity.getTagsId()));
This makes a lot of repetition in the code, because between create and update method of my entity in service, there is a lot of identical code.
Problem 2:
Based on my problem 1, how can I easily map my two DTOs to the same entity without repeating the code 2x?
Code example - PostService (see comment)
This is an example for the update, but there will be almost identical code for the create, so how do I proceed?
#Transactional
public Post update(Integer postId, UpdatePostRequest request) {
return Optional.ofNullable(this.getById(postId)).map(post -> {
// here how to map non-null properties of my request
// into my post taking in consideration my comment above?
postDAO.save(post);
return post;
}).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
================================
UPDATE:
As requested, found the code bellow.
The controller:
#RestController
#RequestMapping("/v1/posts")
public class PostController {
RequestMapping(method = RequestMethod.POST, consumes = "application/json", produces = "application/json; charset=UTF-8")
public ResponseEntity<Object> update(#Valid #RequestBody CreatePostRequest createPostRequest) {
Post post = postService.create(createPostRequest);
return new ApiResponseHandler(new PostDTO(post), HttpStatus.OK).response();
}
RequestMapping(value = "/{postId}", method = RequestMethod.PUT, consumes = "application/json", produces = "application/json; charset=UTF-8")
public ResponseEntity<Object> update(#Valid #RequestBody UpdatePostRequest updatePostRequest, #PathVariable Integer postId) {
Post post = postService.update(postId, updatePostRequest);
return new ApiResponseHandler(new PostDTO(post), HttpStatus.OK).response();
}
}
CreatePostRequest :
#Data
public class CreatePostRequest {
#NotNull
#Size(min = 10, max = 30)
private Sting title;
#NotNull
#Size(min = 50, max = 600)
private String description
#NotNull
#ValidDateString
private String expirationDate;
#NotNull
private List<TagDTO> tags;
public List<Integer> getTagIds() {
return this.getTags().stream().map(TagDTO::getId).collect(Collectors.toList());
}
}
UpdatePostRequest :
#Data
public class UpdatePostRequest {
#Size(min = 10, max = 30)
private Sting title;
#Size(min = 50, max = 600)
private String description
#ValidDateString
private String expirationDate;
private List<TagDTO> tags;
public List<Integer> getTagIds() {
return this.getTags().stream().map(TagDTO::getId).collect(Collectors.toList());
}
}
The service :
#Service
#Transactional
public class PostService {
#Transactional
public Post create(CreatePostRequest request) {
ModelMapper modelMapper = new ModelMapper();
Post post = modelMapper.map(request, Post.class);
// map will not work for tags : how to check that tags exists in database ?
return postDAO.save(post);
}
#Transactional
public Post update(Integer postId, UpdatePostRequest request) {
return Optional.ofNullable(this.getById(postId)).map(post -> {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setSkipNullEnabled(true);
modelMapper.map(request, post);
// map will not work for tags : how to check that tags exists in database ?
postDAO.save(post);
return post;
}).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
}
To avoid duplication of two similar DTOs you could use #Validated group validations. This allows you to actively set which validations are to be done on each property. You can read more about this in the following online resource https://www.baeldung.com/spring-valid-vs-validated. You would begin with the creation of two market interfaces:
interface OnCreate {}
interface OnUpdate {}
You can then use these marker interfaces with any constraint annotation in your common DTO:
#Data
public class CreateOrUpdatePostRequest {
#NotNull(groups = OnCreate.class)
#Size(min = 10, max = 30, groups = {OnCreate.class, OnUpdate.class})
private Sting title;
#NotNull(groups = OnCreate.class)
#Size(min = 50, max = 600, groups = {OnCreate.class, OnUpdate.class})
private String description
#NotNull(groups = OnCreate.class)
#ValidDateString(groups = {OnCreate.class, OnUpdate.class})
private String expirationDate;
#NotNull(groups = OnCreate.class)
private List<TagDTO> tags;
public List<Integer> getTagIds() {
return this.getTags().stream().map(TagDTO::getId).collect(Collectors.toList());
}
}
Finally, you just need to annotate your methods in the Controller accordingly:
#RestController
#RequestMapping("/v1/posts")
#Validated
public class PostController {
#RequestMapping(method = RequestMethod.POST, consumes = "application/json", produces = "application/json; charset=UTF-8")
public ResponseEntity<Object> update(#Validated(OnCreate.class) #RequestBody CreateOrUpdatePostRequest createPostRequest) {
Post post = postService.create(createPostRequest);
return new ApiResponseHandler(new PostDTO(post), HttpStatus.OK).response();
}
#RequestMapping(value = "/{postId}", method = RequestMethod.PUT, consumes = "application/json", produces = "application/json; charset=UTF-8")
public ResponseEntity<Object> update(#Validated(OnUpdate.class) #RequestBody CreateOrUpdatePostRequest updatePostRequest, #PathVariable Integer postId) {
Post post = postService.update(postId, updatePostRequest);
return new ApiResponseHandler(new PostDTO(post), HttpStatus.OK).response();
}
}
With this, you can have a single mapping function.
Still, keep in mind that using validation groups can easily become an anti-pattern given that we are mixing different concerns. With validation groups, the validated entity has to know the validation rules for all the use cases it is used in. Having said that, I usually avoid using validation groups unless it is really necessary.
Regarding tags I guess your only option is to query the database. The ones that do not exist you should create them (I guess), so something along the following lines:
List<Integer> tagsId = createOrUpdatePostRequest.getTagsId();
List<Tag> tags = tagService.findAllByIds(tagsId);
List<Integer> nonExistentTagsId = tagsId.stream().filter(id -> tags.stream().noneMatch(tag -> tag.getId().equals(id)));
if (!nonExistentTagsId.isEmpty()) {
// create Tags and add them to tags List
}

ServiceResponse mocked which gives a null value and not expected this null

I'm writing j-unit Test-cases for my services and in which i couldn't mock service Response properly, Which is giving me a null. can somebody help me in this issue.
public ResponseEntity<Void> lockGet(
#ApiParam(value = "Unique identifier for this request.", required = true) #RequestHeader(value = "service-id", required = true) String serviceId,
#ApiParam(value = "Logged in userid.", required = true) #RequestHeader(value = "user-id", required = true) String userId,
#ApiParam(value = "Unique messageid.", required = true) #RequestHeader(value = "message-id", required = true) String messageId,
#RequestHeader(value = "access-token", required = true) String accessToken,
#ApiParam(value = "Unique id of the doamin of the entity", required = true) #RequestParam(value = "lockDomainId", required = true) Long lockDomainId,
#ApiParam(value = "Unique id of the entity to be fetched", required = true) #RequestParam(value = "lockEntityId", required = true) Long lockEntityId,
HttpServletRequest request, HttpServletResponse response) {
ResponseEntity<Void> result = null;
if (request.getAttribute("user-id") != null)
userId = (String) request.getAttribute("user-id");
String logContext = "||" + lockDomainId + "|" + lockEntityId + "||";
ThreadContext.put("context", logContext);
long t1 = System.currentTimeMillis();
LOG.info("Method Entry: lockGet" + logContext);
ServiceRequest serviceRequest = AppUtils.mapGetRequestHeaderToServiceRequest(serviceId, userId, lockDomainId,
lockEntityId);
try {
ServiceResponse serviceResponse = lockService.getLock(serviceRequest);
// set all the response headers got from serviceResponse
HeaderUtils.setResponseHeaders(serviceResponse.getResponseHeaders(), response);
result = new ResponseEntity<Void>(HeaderUtils.getHttpStatus(serviceResponse));
} catch (Exception ex) {
LOG.error("Error in lockGet", ex);
result = new ResponseEntity<Void>(HttpStatus.INTERNAL_SERVER_ERROR);
}
ThreadContext.put("responseTime", String.valueOf(System.currentTimeMillis() - t1));
LOG.info("Method Exit: lockGet");
return result;
}
#Test
public void testLockGetForError() {
request.setAttribute("user-id","TestUser");
ServiceRequest serviceRequest = new ServiceRequest();
serviceRequest.setUserId("TestUser");
ServiceResponse serviceResponse = new ServiceResponse();
LockService service = Mockito.mock(LockService.class);
when(service.getLock(serviceRequest)).thenReturn(serviceResponse);
// ServiceResponse serviceResponse = lockService.getLock(serviceRequest);
ResponseEntity<Void> result = new ResponseEntity<Void>(HeaderUtils.getHttpStatus(serviceResponse));
ResponseEntity<Void> lockGet = lockApiController.lockGet("1234", "TestUser", "TestMessage", "TestTkn", 12345L, 12345L, request, response);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, lockGet.getStatusCode());
}
I tried in different scenario's which couldn't fix this issue. Can someone help me out. Thanks in advance.
From the code that you have put , the issue that i see is that you are actually mocking the LockService object but when calling the lockApiController.lockGet method the code is not actually working with the mocked LockService since lockApiController has an LockService object of it's own.
One way to solve this issue is to inject the mocked LockService
object into the lockApiController object using #Spy. This way
when the getLock() is called it will be actually called on the
mocked object and will return the mock response provided.
So in your test :
#Test
public void testLockGetForError() {
LockService service = Mockito.mock(LockService.class);
LockApiController lockApiController = Mockito.spy(new LockApiController(service));
request.setAttribute("user-id","TestUser");
ServiceRequest serviceRequest = new ServiceRequest();
serviceRequest.setUserId("TestUser");
ServiceResponse serviceResponse = new ServiceResponse();
when(service.getLock(serviceRequest)).thenReturn(serviceResponse);
// ServiceResponse serviceResponse = lockService.getLock(serviceRequest);
ResponseEntity<Void> result = new ResponseEntity<Void>(HeaderUtils.getHttpStatus(serviceResponse));
ResponseEntity<Void> lockGet = lockApiController.lockGet("1234", "TestUser", "TestMessage", "TestTkn", 12345L, 12345L, request, response);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, lockGet.getStatusCode());
}
So you can try passing the mocked LockService object to the spy object.
Another way is to try using the #InjectMocks to inject the mocked
object into the LockApiController.
#InjectMocks marks a field on which injection should be performed. Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection – in this order. If any of the given injection strategy fail, then Mockito won’t report failure.
For example:
#Mock
Map<String, String> wordMap;
#InjectMocks
MyDictionary dic = new MyDictionary();
#Test
public void whenUseInjectMocksAnnotation_thenCorrect() {
Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");
assertEquals("aMeaning", dic.getMeaning("aWord"));
}
For the class:
public class MyDictionary {
Map<String, String> wordMap;
public MyDictionary() {
wordMap = new HashMap<String, String>();
}
public void add(final String word, final String meaning) {
wordMap.put(word, meaning);
}
public String getMeaning(final String word) {
return wordMap.get(word);
}
}
For both of these to work , you must be having a constructor or appropriate setters to set the mock object to the LockApiController class.
Reference : https://howtodoinjava.com/mockito/mockito-annotations/

How to force Jackson deserialize field values to lower case

I have spring application which expose REST endpoint, lets name it "doAction". As the request it consumes object:
class Person{
private String name;
private String email;
}
Some clients can call this endpoint by passing data with different practice of writing words, like:
Peter_1
name = Peter
email = peter#gmail.com (lower case)
Mark_2
name = mark
email = MARK#gmail.com (upper case)
Julia_3
name = julia
email = JuliaToward#gmail.com (camel case)
Is there some approach to force all income data be parsed to lowercase(lets assume all fields are Strings)?
So as a result I desire to have:
Peter_1
name = peter
email = peter#gmail.com
Mark_2
name = mark
email = mark#gmail.com
Julia_3
name = julia
email = juliatoward#gmail.com
Solution for Jackson is appreciated.
Short answer Call toLower in the setter
Here is an example:
class Animal
{
private String name;
public void setName(final String newValue)
{
StringUtils.trimToNull(StringUtils.lowerCase(newValue));
}
}
I also recommend either trimToNUll or trimToEmpty.
If you are using Spring Data Rest with spring mvc and you want all incoming string data to be in lower case then define following
public class StringSerializer extends StdDeserializer<String>{
public StringSerializer() {
this(null);
}
public StringSerializer(Class<String> vc) {
super(vc);
}
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonToken t = p.getCurrentToken();
if (t==JsonToken.VALUE_STRING){
String receivedValue = p.getText();
if (receivedValue == null)
return null;
else
return receivedValue.toLowerCase();
}else{
return null;
}
}
}
And following:
#Configuration
public class RestDataConfig extends RepositoryRestMvcConfiguration {
#Override
#Bean
public ObjectMapper halObjectMapper() {
ObjectMapper mapper = super.halObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new StringSerializer());
mapper.registerModule(module);
return mapper;
}
}

#PathVariable Validation in Spring 4

How can i validate my path variable in spring. I want to validate id field, since its only single field i do not want to move to a Pojo
#RestController
public class MyController {
#RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public ResponseEntity method_name(#PathVariable String id) {
/// Some code
}
}
I tried doing adding validation to the path variable but its still not working
#RestController
#Validated
public class MyController {
#RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public ResponseEntity method_name(
#Valid
#Nonnull
#Size(max = 2, min = 1, message = "name should have between 1 and 10 characters")
#PathVariable String id) {
/// Some code
}
}
You need to create a bean in your Spring configuration:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
You should leave the #Validated annotation on your controller.
And you need an Exceptionhandler in your MyController class to handle theConstraintViolationException :
#ExceptionHandler(value = { ConstraintViolationException.class })
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleResourceNotFoundException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
StringBuilder strBuilder = new StringBuilder();
for (ConstraintViolation<?> violation : violations ) {
strBuilder.append(violation.getMessage() + "\n");
}
return strBuilder.toString();
}
After those changes you should see your message when the validation hits.
P.S.: I just tried it with your #Size validation.
To archive this goal I have apply this workaround for getting a response message equals to a real Validator:
#GetMapping("/check/email/{email:" + Constants.LOGIN_REGEX + "}")
#Timed
public ResponseEntity isValidEmail(#Email #PathVariable(value = "email") String email) {
return userService.getUserByEmail(email).map(user -> {
Problem problem = Problem.builder()
.withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE)
.withTitle("Method argument not valid")
.withStatus(Status.BAD_REQUEST)
.with("message", ErrorConstants.ERR_VALIDATION)
.with("fieldErrors", Arrays.asList(new FieldErrorVM("", "isValidEmail.email", "not unique")))
.build();
return new ResponseEntity(problem, HttpStatus.BAD_REQUEST);
}).orElse(
new ResponseEntity(new UtilsValidatorResponse(EMAIL_VALIDA), HttpStatus.OK)
);
}

How to return a list of object as Json in Spring MVC

I'm trying to get a list of objects to render on a Spring 3 MVC app and would like to do this via Ajax.
So in my Spring class I have:
#RequestMapping(value = "/viewSearchEnquiriesAjax", method = RequestMethod.GET, headers = "Accept=application/json")
public #ResponseBody List<Enquiry> getEnquiriesBySearchAjax(#RequestParam String name) {
Search search = new Search();
search.setFirstName(name);
return searchEnquiries(search);
}
But I get a 500 (Internal Server Error) when this is run. This manifests itself when I'm debugging in the browser as 'GET http://localhost:8080/SpringMVC/viewSearchEnquiriesAjax?name=peter 500 (Internal Server Error)'
I can successfully return a single object with no error. Can the Spring Json mapper(Jackson) convert correctly? Am I missing something fundamental?
My javascript is as follows:
function doAjaxPost() {
// get the form values
var firstName = $('#firstName').val();
$.getJSON("/SpringMVC/viewSearchEnquiriesAjax", { name: firstName }, function(result) {
alert("Success");
});
}
My Enquiry object is an Entity:
#Entity
#Table(name = "enquiries")
public class Enquiry implements java.io.Serializable{
private static final long serialVersionUID = -5093725544297637792L;
protected Long id;
protected Date created = new Date();
...
...
public Enquiry() {
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", unique = true, nullable = false)
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
...
...
For Jackson you have to create a strongly typed list class because of type erasure:
public class EnquiryList extends ArrayList<Enquiry> {
}
Then return this list from your controller:
#RequestMapping(value = "/viewSearchEnquiriesAjax", method = RequestMethod.GET, headers = "Accept=application/json")
public #ResponseBody EnquiryList getEnquiriesBySearchAjax(#RequestParam String name) {
EnquiryList list = new EnquiryList();
...
return list;
}
Also check out this answer on a similar question.

Resources