I'm writing tests for a bean that is a parameter in #RestController's method.
Bean POJO:
public class AddTownRequestBean
{
#NotEmpty(message = "INVALID_REQUEST")
#Length(min = 1, max = 30, message = "PARAMETER_OUT_OF_BOUNDS")
private String name;
#NotEmpty(message = "INVALID_REQUEST")
#Length(min = 3, max = 4, message = "PARAMETER_OUT_OF_BOUNDS")
private String typeCreated;
#DateTimeFormat(pattern = "yyyy-MM-dd") //style = "S-", iso = DateTimeFormat.ISO.DATE,
private String foundationDate;
getters and setters...
}
My question is related to #DateTimeFormat annotation. In documentation it is stated that this annotation:
Can be applied to java.util.Date,
java.util.Calendar, Long (for millisecond timestamps) as well as
JSR-310 java.time and Joda-Time value types.
As one can see, there's no support of simple String type, but my POJO's date field is String. I already tested using #DateTimeFormat as outlined above, also with commented parameters, mutually excluded every time. And obviously it didn't work out.
So the question itself - is there any annotation or similar workaround to add a (let's call it) "validator" for specific date format in a String type variable that's meant to be a date?
This question or similar one previously asked and answered. Below is the link to previous question. Please see if that answer helps you.
Java String Date Validation Using Hibernate API
You can create custom validator annotation for this case. Example
DateTimeValid.class
#Constraint(validatedBy = DateTimeValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface DateTimeValid{
public String message() default "Invalid datetime!";
public String fomart() default "MM/dd/yyyy";
public Class<?>[] groups() default {};
public Class<? extends Payload>[] payload() default {};
}
DateTimeValidator.class
public class DateTimeValidator implements ConstraintValidator<DateTimeValid, String> {
private String dateFormat;
#Override
public void initialize(DateTimeValid constraintAnnotation) {
dateFormat = constraintAnnotation.fomart();
}
#Override
public boolean isValid(String strDate, ConstraintValidatorContext context) {
try {
DateFormat sdf = new SimpleDateFormat(this.dateFormat);
sdf.setLenient(false);
sdf.parse(strDate);
} catch (Exception e) {
return false;
}
return true;
}
}
Usage
#DateTimeValid(fomart="...", message="...")
private String foundationDate;
Edit: #Ramu: This code from my project I have done before. But yeah, I read the link and it is the same idea with above code
Related
Use Case:
Let's assume this is my POJO:
class Sample{
String field1;
String field2;
double field3;
LocalDateTime field4;
LocalDateTime field5;
//...getters, setters and parameterised constructor
}
I am reading certain values from an external file and creating a POJO using a parameterised constructor. All the fields have certain validation constraints for them.
What I am looking for is a way for those constraints to be evaluated automatically when I am creating an object using the parameterised constructor. If one or more validation constraints fail, it should throw an error.
What I have tried so far:
I have tried the Bean Validation approach in Spring by creating my own annotation and validator. The code is below:
POJO
#ValidChecker(groups = Default.class)
class Sample{
String field1;
String field2;
double field3;
LocalDateTime field4;
LocalDateTime field5;
//...getters, setters and parameterised constructor
}
ValidChecker Annotation
#Constraint(validatedBy = DataValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidChecker {
String message() default "Data is not valid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
DataValidator.java
#SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
public class DataValidator implements ConstraintValidator<ValidChecker, ValidationData> {
#Override
public void initialize(ValidChecker constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
#Override
public boolean isValid(ValidationData validationData, ConstraintValidatorContext constraintValidatorContext) {
if (validationData == null) {
return false;
}
if (BigDecimal.valueOf(validationData.getField3()).scale() != 2) {
return false;
}
if (validationData.getField5().isBefore(validationData.getField4())) {
return false;
}
return true;
}
}
The above code didn't work.
Suggestions Needed
Problem with the above approach
Alternate approach using Spring
Alternate approach by using some third party library
I looked quite a bit but couldn't find an approach without Spring bean validation. Can someone please help?
You could use the Bean Validation API directly in your constructor:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Sample>> violations = validator.validate(this);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
I have a class with this field in:
#DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate passDate;
I created two LocalDate variables which I want to check between (for example)
LocalDate a = LocalDate.of(1995, 1, 01);
LocalDate b = LocalDate.of(2140, 12, 31);
I was using #PastOrPresent but that does not stop users from entering a date like year 3050.
I started making a method in the domain class, where the passDate field resides, however I really do not know where the validation goes and how to call this validation method. (Here is a snippet of what I was trying to do but unsure where to put it? (maybe its wrong too!))
if (!(passDate.isBefore(a) && passDate.isAfter(b))) {
return passDate; }
Wasn't sure where this goes? What method? How do I call this validation? or is there another way. I have looked online for so long and can't figure out what to do.
I have a thymeleaf form with this field (which was using the PastOrPresent validation to return an error message on submit)
<div class="form-group">
<label for="pass_date">Enter the pass date</label>
<input type="date" th:field="*{passDate}" name="pass_date" id="pass_date"
class="form-control"/>
<p class="text-danger" th:if="${#fields.hasErrors('passDate')}" th:errors="*{passDate}"></p>
</div>
Here is the post controller
#PostMapping("/admin/examform")
public String createExamForm(#ModelAttribute("examform") #Valid Examform examform,
BindingResult bindingResult,
#AuthenticationPrincipal final User user, Model model){
if (bindingResult.hasErrors()) {
System.out.println(bindingResult.getAllErrors());
model.addAttribute("examform", examform);
return "examformhtml";
}else{
examformservice.createExamForm(examform);
model.addAttribute("loggedInUsername", user.getUsername());
return "examformsuccess";
}
}
Where examformservice is a class variable of my service which links to my repository which is
#Override
public void createExamForm(Examform examform) {
String sql = "UPDATE examform SET passDate=? WHERE studentId=?";
jdbcTemplate.update(sql, examform.getPassDate(), examform.getStudentId());
}
Where would I put the validation? and what would the input be?
If you want a JSR annotation you can work your way from this one:
#Constraint(validatedBy=AfterValidator.class)
#Target({ElementType.FIELD, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface After {
String message() default "must be after {value}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String value();
}
And its validator:
public class AfterValidator implements ConstraintValidator<After, LocalDate> {
private LocalDate date;
public void initialize(After annotation) {
date = LocalDate.parse(annotation.value());
}
public boolean isValid(LocalDate value, ConstraintValidatorContext context) {
boolean valid = true;
if (value != null) {
if (!value.isAfter(date)) {
valid = false;
}
}
return valid;
}
}
The above is exclusive (the exact date will be invalid), you may want to tweak it.
To use it is only to add annotation in model bean:
#After("1995-01-01")
private LocalDate passDate;
The inverse (#Before) I leave you as an exercise :)
You need to check this before you assign it to the field. So in your form submit you do something like that:
LocalDate input = ...;
if (!(input.isBefore(a) && input.isAfter(b))) {
// set the field
} else {
// handle error here
// throw exception or just print something
}
I'm creating a custom ConstraintValidator to validate that my JodaTime object's hours are within a certain window when entered from a spring form.
My annotation:
#Target({ElementType.METHOD, ElementType.FIELD})
#Documented
#Constraint(validatedBy = InputHoursValidator.class)
#Retention(RetentionPolicy.RUNTIME)
public #interface InputHoursConstraint {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
My Validator
public class InputHoursValidator implements ConstraintValidator<InputHoursConstraint, DateTime> {
private static final DateTimeFormatter HOURS_TIME_FORMAT = DateTimeFormat.forPattern("hh:mma");
private static final String EARLIEST_START_TIME = "5:00pm";
private static final String LATEST_END_TIME = "4:00am";
#Override
public void initialize(InputHoursConstraint constraintAnnotation) {
}
#Override
public boolean isValid(DateTime value, ConstraintValidatorContext context) {
return !value.isBefore(DateTime.parse(EARLIEST_START_TIME, HOURS_TIME_FORMAT))
&& !value.isAfter(DateTime.parse(LATEST_END_TIME, HOURS_TIME_FORMAT).plusDays(1));
}
}
And my mojo with the annotation
public class HoursTrackingForm {
#NotNull(message = "Please enter a valid time in AM or PM")
#DateTimeFormat(pattern = "hh:mma")
#InputHoursConstraint(message = "Start time was before 5:00pm or after 4:00am")
private DateTime startTime;
#NotNull(message = "Please enter a valid time in AM or PM")
#DateTimeFormat(pattern = "hh:mma")
#InputHoursConstraint(message = "End time was before 5:00pm or after 4:00am")
private DateTime endTime;
//getters and setters
}
It all looks fine to me but when I submit my object for validation, DateTime in the validator is always null.
My issues were two fold.
1) if I was testing a null scenario I didn't realize all of the constraints still get validated even if one finds an error. Therefore while not null caused an validation error my custom constraint would still throw an NPE. Solution for that was to remove the #NotNull and do that check as well in #InputHoursConstraint.
2) In my validation annotation I added ElementType.TYPE and ElementType.LOCAL_VARIABLE in #Target which appears to allow it to work. Still doing research into why because based on my understanding I only needed ElementType.FIELD
I know that exist annotation #Future.
If I annotate field with this annotation
#Future
private Date date;
date must be in future means after current moment.
Now I need to validate that date was at least 24 hours after current moment.
How can I make it?
AfterTomorrow.java:
#Target({ FIELD, METHOD, PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = AfterTomorrowValidator.class)
#Documented
public #interface AfterTomorrow {
String message() default "{AfterTomorrow.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
AfterTomorrowValidator.java:
public class AfterTomorrowValidator
implements ConstraintValidator<AfterTomorrow, Date> {
public final void initialize(final AfterTomorrow annotation) {}
public final boolean isValid(final Date value,
final ConstraintValidatorContext context) {
Calendar c = Calendar.getInstance();
c.setTime(value);
c.add(Calendar.DATE, 1);
return value.after(c.getTime());
}
}
Additionally, you can add the default AfterTomorrow.message message in ValidationMessages.properties
Finally, annotate your field:
#AfterTomorrow
private Date date;
I have a simple bean, i.e.:
public class MyBean {
private boolean selected;
private String someString;
...
}
So if selected is true, I want someString to be #NotNull etc. .
Any hints, links how to achieve this behaviour?
Thanks
Jonny
If you’re using Spring Framework then you can use Spring Expression Language (SpEL) for that. I’ve wrote small library that provides JSR-303 validator based on SpEL that makes cross-field validations very easy. Take a look at https://github.com/jirutka/validator-spring.
And there’s example for your case:
#SpELAssert(value = "someString != null", applyIf = "selected",
message = "{validator.missing_some_string}")
public class MyBean {
private boolean selected;
private String someString;
...
}
Actually this was too easy. Try something more interesting, maybe an equality of password fields when one of them is not null.
#SpELAssert(value = "password.equals(passwordVerify)",
applyIf = "password || passwordVerify",
message = "{validator.passwords_not_same}")
public class User {
private String password;
private String passwordVerify;
}
And you can even use your own “helper methods” in these expressions!
Compared to Hibernate Validator’s #ScriptAssert annotation, this is pure Java solution, it’s not using any JSR-223 compliant scripting language which may be a little problematic. On the other side, this solution is interesting only for Spring-based applications.
You could do this by annotating MyBean with a custom validator, for example:
#ValidMyBean
public class MyBean {
private boolean selected;
private String someString;
...
}
ValidMyBean:
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyBeanValidator.class)
public #interface ValidMyBean {
boolean allViolationMessages() default true;
Class<?>[] constraints() default {};
Class<?>[] groups() default {};
String message() default "{ValidMyBean.message}";
Class<? extends Payload>[] payload() default {};
}
MyBeanValidator:
public final class MyBeanValidator implements
ConstraintValidator<ValidMyBean, MyBean> {
#Override
public void initialize(
#SuppressWarnings("unused") final ValidMyBean constraintAnnotation) {
}
#Override
public boolean isValid(final MyBean value,
final ConstraintValidatorContext context) {
boolean isValid = true;
//your validation here
return isValid;
}
}