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
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.
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
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?
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.
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;
}