Spring Boot request body validation - spring-boot

I am working on Spring REST API and have following controller:
#RestController
#RequestMapping(
value = "/api/Test",
produces = "application/json"
)
public class MyController {
#RequestMapping(method = RequestMethod.POST)
public Response serviceRequest(#Valid #RequestBody ServiceRequest myServiceRequest) {
....
}
}
ServiceRequest has following structure:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class ServiceRequest {
#NotBlank
private LocalDate fromDate;
#NotBlank
private LocalDate toDate;
}
My task is to introduce validation based on combination of fromDate and toDate field's values: if time period between them is longer that 1 week then validation should fail.
What is the best way to archive this please?

You may create a custom constraint validator that will validate the required conditions before processing the request.
DatesDifference.java
#Constraint(validatedBy = DatesDifferenceValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface DatesDifference {
String message() default "Dates difference is more than a week";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
DatesDifferenceValidator.java
#Component
public class DatesDifferenceValidator implements ConstraintValidator<DatesDifference, ServiceRequest> {
#Override
public boolean isValid(ServiceRequest serviceRequest, ConstraintValidatorContext context) {
System.out.println("validation...");
if (!(serviceRequest instanceof ServiceRequest)) {
throw new IllegalArgumentException("#DatesDifference only applies to ServiceRequest");
}
LocalDate from = serviceRequest.getFromDate();
LocalDate to = serviceRequest.getToDate();
long daysBetween = ChronoUnit.DAYS.between(from, to);
System.out.println("daysBetween "+daysBetween);
return daysBetween <= 7;
}
}
ServiceRequest.java
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#DatesDifference
public class ServiceRequest {
#NotBlank
private LocalDate fromDate;
#NotBlank
private LocalDate toDate;
}

Related

Desrialzing JSON in spring boot, where a field is the combination of two fields

I have a controller with the following signiture:
public HttpEntity<RepresentationModel> confirmRegistration(#Valid #RequestBody RegistrationRequest request{}
the RegistrationRequest Json looks like this
{
//other fields
"countryCode":"44",
"mobileNumber": "07545878096"
}
I am trying to write a custom deserializer for this json
My mobileNumber class looks like this:
#Getter
#Setter
#EqualsAndHashCode
#ToString
#AllArgsConstructor
public class MobileNumber {
#JsonProperty("mobilePhoneNumber")
#JsonAlias("mobileNumber")
String number;
#JsonProperty(value = "countryCode", defaultValue = "44")
String countryCode;
}
and a request object like so:
public class RegistrationRequest {
//other fields
#JsonDeserialize(using = MobileNumberDeserializer.class)
#MobileNumberValidator
private final MobileNumber mobilePhoneNumber;
}
where the MobileNumberDeserializer looks like this:
public class ContactNumberDeserializer extends StdDeserializer<MobileNumber> {
private static final long serialVersionUID = 1L;
protected ContactNumberDeserializer() {
super(MobileNumber.class);
}
#Override
public MobileNumber deserialize(JsonParser jsonParser, DeserializationContext ctxt)
throws IOException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
String mobileNumber = "";
if (node.has("mobilePhoneNumber")) {
mobileNumber = node.get("mobilePhoneNumber").asText();
} else if (node.has("phoneNumber")) {
mobileNumber = node.get("phoneNumber").asText();
} else if (node.has("mobileNumber")) {
mobileNumber = node.get("mobileNumber").asText();
}
String countryCode = node.get("countryCode").asText();
return new MobileNumber(mobileNumber, countryCode);
}
when the ContactNumberDeserializer is invoked by the controller,jsonParser.getCodec().readTree(jsonParser); it's just the mobilePhoneNumber node and cant access countryCode.
Quick check if ContactNumber and MobileNumber are the same classes.
Ideally it should be
public class ContactNumberDeserializer extends StdDeserializer<MobileNumber {
...
}
In your MobileNumber class:
#Getter
#Setter
#EqualsAndHashCode
#ToString
#AllArgsConstructor
public class MobileNumber {
#JsonProperty("mobilePhoneNumber")
#JsonAlias("mobileNumber")
String number;
#JsonProperty("countryCode")
String countryCode = "44";
}
Update JsonProperty annotation like above for countryCode. Hope it helps!
You don't need to write ContactNumberDeserializer. If you wrote your class MobileNumber it would simply work.

Spring Boot Lombok API not serializing

I have a simple Spring Boot controller with a simple Object, which is annotated with Lombok, when I tried to post data to the controller the object to not serializing.
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public class Employee extends BaseDomain {
private String firstName;
private String middleName;
private String lastName;
private String email;
private String mobileNo;
#PostMapping
public Employee saveEmployee(Employee employee) {
log.debug("Employee save {}", employee);
return employeeService.saveOrUpdateEmployee(employee);
}
}
#PostMapping
public Employee saveEmployee(#Requestbody Employee employee) {
log.debug("Employee save {}", employee);
return employeeService.saveOrUpdateEmployee(employee);
}
#Requestbody is missing

How to extend Panache with static method?

Hello. I want to write a static method in class with #MapperdSupperclass. such as findByName. so the child object can use method to query itself.
But I got error. It will query BaseUpdatableEntity not the childObject.The code like this.
#MappedSuperclass
#Slf4j
#ToString
public abstract class BaseUpdatableEntity extends BaseEntity {
#Getter
protected LocalDateTime created;
#Getter
protected LocalDateTime updated;
#Getter
protected String createdBy;
#Getter
protected String updatedBy;
#Version
#Setter
#Getter
protected Long lockVersion = 1L;
#Override
protected void prePersist() {
super.prePersist();
created = LocalDateTime.now();
createdBy = MDCScope.getUsername();
}
#PreUpdate
protected void preUpdate() {
updated = LocalDateTime.now();
updatedBy = MDCScope.getUsername();
}
#Override
public Object clone() throws CloneNotSupportedException {
BaseUpdatableEntity updatableEntity = (BaseUpdatableEntity) super.clone();
updatableEntity.setLockVersion(1L);
return updatableEntity;
}
public BaseUpdatableEntity getByName(String name) {
return find("name", name).<BaseUpdatableEntity>singleResultOptional().orElse(null);
}
}
so I read the PanacheEntityBase code. I find the static method with #GenerateBridge
But I don't konw how to archive it with myself?

Spring Boot test entity field validation with custom validation

Setup:
Spring Boot 2.3.3 (spring-boot-starter-validation)
JUnit 5 Jupiter
Lombok
Entity fields can be annotated with simple javax.validation constraints like #Positive.
#Entity
#Data
#Builder
public class Person {
#Id
#GeneratedValue
private long id;
#Column
#Positive(message="not positive")
private int age;
}
This can be tested quite easily:
public class PersonTest {
private Validator validator;
#BeforeEach
public void before() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
#Test
public void invalid_becauseNegativeAge() {
Person uut = Person.builder().age(-1).build();
Set<ConstraintViolation<Person>> actual = validator.validate(uut);
assertThat(actual.iterator().next().getMessage()).isEqualTo("not positive");
}
}
Now I add a custom validator to Person:
#Entity
#Data
#Builder
public class Person {
#Id
#GeneratedValue
private long id;
#Column
#Positive(message="not old enough")
private int age;
#ComplexValueConstraint
private String complexValue;
}
Custom annotation:
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = ComplexValueValidator.class)
public #interface ComplexValueConstraint {
String message() default "too simple";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Custom validator:
class ComplexValueValidator implements ConstraintValidator<ComplexValueConstraint, Person> {
#Override
public boolean isValid(final Person person, final ConstraintValidatorContext context) {
String complexValue = person.getComplexValue();
if (complexValue.length() < 5) {
return false;
}
return true;
}
}
Now, my test from above fails with a
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'ComplexValueConstraint' validating type 'java.lang.String'. Check configuration for 'complexValue'
How can I make this test to work again? I know I could test the validator in isolation, but then my simple annotations like #Positive would remain untested.
Is it because I create the validator in the test class manually? #Autowired won't work somehow.
You are declaring that ComplexValueValidator validates objects of type Person:
ComplexValueValidator implements ConstraintValidator<ComplexValueConstraint, Person>
but the annotation is applied to a field of type String instead of a field of type Person:
#ComplexValueConstraint
private String complexValue;
Hence the message that the constraint can not be applied to a java.lang.String.
Try changing complexValue to be of type Person instead of type String.

Spring 4.1.7 validate request body

I know this issue has been around there in other post, but after applying the fiux suggested was not working.
I am using spring 4.1.7 version, i want to validate the RequestBody from post rest call. For doing this i tried following set of codes, but it was not working as i expected.
My Request body pojo classes.
ParentPojo.class
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Builder
#ToString
#Validated
public class ParentPojo<T> implements Serializable{
#NotNull
private String cNumber;
#NotNull
private String statusCode;
#NotNull T child;
}
ChildPojo.class
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Builder
#ToString
#Validated
public class ChildPojo{
#NotNull
private String name;
#NotNull
private String address;
#NotNull
private String pin;
}
Controller:
Adding only methods
#Autowired
#Qualifier("validator")
private Validator validator;
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
#RequestMapping(produces = { "application/json", "application/xml" }, consumes ={MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}, method = { RequestMethod.POST })
#ResponseStatus(HttpStatus.CREATED)
public Messageresponse<ChildPojo> create(#NotNull(groups = {ParentPojo.class, ChildPojo.class})
#Valid #Validated({ParentPojo.class, ChildPojo.class}) #RequestBody final ParentPojo<ChildPojo> ParentPojo, BindingResult bindingResult) {
System.out.println("new version 8="+bindingResult.hasErrors());
validator.validate(ParentPojo, bindingResult);
if(bindingResult.hasErrors()) {
System.out.println("Non formated form stuff.");
}
return service.create(ParentPojo);
}
#RequestMapping(value = "/{create}", produces = { "application/json", "application/xml" }, consumes ={MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}, method = { RequestMethod.POST })
#ResponseStatus(HttpStatus.CREATED)
public Messageresponse<ChildPojo> create1(#NotNull #Valid #RequestBody final ParentPojo<ChildPojo> ParentPojo, BindingResult bindingResult) {
System.out.println("new version 8="+bindingResult.hasErrors());
validator.validate(ParentPojo, bindingResult);
if(bindingResult.hasErrors()) {
System.out.println("Non formated form stuff.");
}
return service.create(ParentPojo);
}
application context xml:
<mvc:annotation-driven validator="validator">
</mvc:annotation-driven>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
jar file tried:
hibernate-validator-4.3.0.Final
hibernate-validator-4.1.0.Final
hibernate-validator-4.0.2.GA
validation-api-1.1.0.Final
spring-context-4.1.7.RELEASE
But nothing was working with all the above combination for the request below:
in below request "pin" is missed and the controller method #Valid #RequestBody expected to handle this request as Bad Request. instead it is accepting the request body and processing further.
{
"cNumber" : "ff",
"statusCode" : "ddd",
"child" : {
"name" : "ll",
"address" : "ll"
}
Look at this question Here
You need to decorate the child pojo as #Valid
public class ParentPojo<T> implements Serializable{
#NotNull
private String cNumber;
#NotNull
private String statusCode;
#Valid
#NotNull
T child;
}

Resources