Unknown property error while using BeanUtils.getProperty() - java

Here's my class.
#DateRange.List({
#DateRange(start = "startDate", end = "endDate", message = "Start date should be earlier than end date.")
})
public class MyClass {
#NotNull
#Pattern(regexp = DateConstants.DATE_FORMAT_REGEX, message = "Invalid date format.")
public String startDate;
#NotNull
#Pattern(regexp = DateConstants.DATE_FORMAT_REGEX, message = "Invalid date format.")
public String endDate;
}
I've added a #DateRange annotation, which is declared as follows.
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = DateRangeValidator.class)
#Documented
public #interface DateRange {
String message() default "{constraints.daterange}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String start();
String end();
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Documented
#interface List {
DateRange[] value();
}
}
And the validator class is
public class DateRangeValidator implements ConstraintValidator<DateRange, Object> {
private String startDateFieldName;
private String endDateFieldName;
#Override
public void initialize(final DateRange constraintAnnotation) {
startDateFieldName = constraintAnnotation.start();
endDateFieldName = constraintAnnotation.end();
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
final String startDate = (String) BeanUtils.getProperty(value, startDateFieldName);
final String endDate = (String) BeanUtils.getProperty(value, endDateFieldName);
return isValidDateRange(startDate, endDate);
}
private boolean isValidDateRange(String start, String end) {
DateFormat dateFormat = new SimpleDateFormat(DateConstants.DATE_FORMAT);
try {
Date startDate = dateFormat.parse(start);
Date endDate = dateFormat.parse(end);
if (startDate.before(endDate)) return true;
} catch (ParseException e) {}
return false;
}
}
The validator checks if the start date is before the end date.
While doing so, the BeanUtils.getProperty() is throwing java.lang.NoSuchMethodException along with Unknown property 'startDate'.
java.lang.NoSuchMethodException: Unknown property 'startDate' on class 'class mypackage.domain.rest.MyClass'
at org.apache.commons.beanutils.PropertyUtilsBean.getSimpleProperty(PropertyUtilsBean.java:1322)
at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:770)
at org.apache.commons.beanutils.BeanUtilsBean.getNestedProperty(BeanUtilsBean.java:715)
at org.apache.commons.beanutils.BeanUtilsBean.getProperty(BeanUtilsBean.java:741)
at org.apache.commons.beanutils.BeanUtils.getProperty(BeanUtils.java:382)
at bd.com.ipay.offer.validation.imp.DateRangeValidator.isValid(DateRangeValidator.java:36)
at org.hibernate.validator.engine.ConstraintTree.validateSingleConstraint(ConstraintTree.java:278)
at org.hibernate.validator.engine.ConstraintTree.validateConstraints(ConstraintTree.java:153)
at org.hibernate.validator.engine.ConstraintTree.validateConstraints(ConstraintTree.java:117)
at org.hibernate.validator.metadata.MetaConstraint.validateConstraint(MetaConstraint.java:84)
at org.hibernate.validator.engine.ValidatorImpl.validateConstraint(ValidatorImpl.java:452)
at org.hibernate.validator.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:397)
at org.hibernate.validator.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:361)
at org.hibernate.validator.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:313)
at org.hibernate.validator.engine.ValidatorImpl.validate(ValidatorImpl.java:139)
at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validate(SpringValidatorAdapter.java:108)
at org.springframework.validation.DataBinder.validate(DataBinder.java:866)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.validateIfApplicable(AbstractMessageConverterMethodArgumentResolver.java:268)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:130)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:99)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128)
But the startDate is there in MyClass. The variable is public and named camel case. Why the problem is occurring? Any idea?

I found the solution. I just had to write the getters of my MyClass. Seems like the attribute was not found because there was not getter function.
So the final MyClass looks like.
#DateRange.List({
#DateRange(start = "startDate", end = "endDate", message = "Start date should be earlier than end date.")
})
public class MyClass {
#NotNull
#Pattern(regexp = DateConstants.DATE_FORMAT_REGEX, message = "Invalid date format.")
public String startDate;
#NotNull
#Pattern(regexp = DateConstants.DATE_FORMAT_REGEX, message = "Invalid date format.")
public String endDate;
public String getStartDate() {
return startDate;
}
public String getEndDate() {
return endDate;
}
}

Related

Csutom annotation to validate Dto contains two list using java validation and spring boot

I have a class Wrapper that contains two arraylist and another properties :
public class Wrapper<T, E> {
private List<T> toInsert = new ArrayList<>();
private List<T> toUpdate = new ArrayList<>();
private Set<E> toDelete = new HashSet<>();
// getter setter ....
}
I have a Class Dto :
public class UserDto {
private long id;
private LocalDateTime createdDate;
#NotBlank
private String registrationNumber;
#NotBlank
private String firstName;
#NotBlank
private String lastName;
#NotBlank
private String email;
#NotBlank
}
I create a validator :
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {CustomerLocationValidator.class})
#Documented
public #interface ValidCustomerLocation {
String message() default "Invalid List userDto";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The implementation of validator annotation :
public class CustomerLocationValidator implements ConstraintValidator<ValidCustomerLocation, Wrapper<UserDto, String>> {
#Autowired
Validator validator;
#Override
public boolean isValid(Wrapper<UserDto, String> value, ConstraintValidatorContext constraintValidatorContext) {
boolean isValid = true;
final Set<ConstraintViolation<UserDto>> constraintViolations = new HashSet();
value.getToInsert().forEach(userDto -> constraintViolations.addAll( validator.validate(userDto)) );
if (!CollectionUtils.isEmpty(constraintViolations)) {
constraintValidatorContext.disableDefaultConstraintViolation();
for (ConstraintViolation<UserDto> violation : constraintViolations) {
constraintValidatorContext.buildConstraintViolationWithTemplate(violation.getMessageTemplate()).addConstraintViolation();
}
isValid = false;
}
return isValid;
}
}
The method in controller :
#PostMapping
public ResponseEntity<Void> save(
#RequestBody #ValidCustomerLocation Wrapper<UserDto, String> itemsUsers) {}
In this controlller I try to send invalid data but the annotation ValidCustomerLocation didn't catch the error , I try to use #Valid but i have the same problem

Spring Boot: Controller doesn't take in account Validator

I'm trying to accomplish the use of a validator into the controller. The two fields origin and destination should be of three capital letters as IATA Code. But it acts without filter, and any request is accepted.
Set of validator interface:
#Documented
#Constraint(validatedBy = IATACodeValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface IATACodeConstraint {
String message() default "Invalid IATA code";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Class of Validator:
public class IATACodeValidator implements ConstraintValidator<IATACodeConstraint, String> {
#Override
public void initialize(IATACodeConstraint iataCode) {
}
#Override
public boolean isValid(String codeField, ConstraintValidatorContext context) {
return codeField != null && codeField.matches("^[A-Z]{3}+$")
&& (codeField.length() == 3);
}
Basic class:
public class CrazyAirRequest {
#IATACodeConstraint
private String origin;
#IATACodeConstraint
private String destination;
private String departureDate;
private String returnDate;
private int passengerCount;
// getters & setters
Controller:
#RestController
#RequestMapping("flights")
#Validated
public class BusyFlightsController {
CrazyAirDatabase crazyAirService;
ToughJetDatabase toughJetService;
#Autowired
public BusyFlightsController(CrazyAirDatabase crazyAirService, ToughJetDatabase toughJetService) {
this.crazyAirService = new CrazyAirDatabase();
this.toughJetService = new ToughJetDatabase();
}
#RequestMapping(value = "/crazy-air-response", method = RequestMethod.GET)
public List<CrazyAirResponse> getCrazyAirResponse(
#RequestParam("origin") String origin,
#RequestParam("destination") String destination,
#RequestParam("departureDate") String departureDate,
#RequestParam("returnDate") String returnDate,
#RequestParam("passengerCount") int passengerCount
) {
CrazyAirRequest crazyAirRequest = new CrazyAirRequest(origin, destination, departureDate, returnDate,
passengerCount);
return crazyAirService.getCrazyAirResponse(crazyAirRequest);
}
You can directly use #Pattern annotation without creating a custom validator for two fields as shown below :
#Pattern(regexp ="^[A-Z]{3}" message ="Invalid IATA code")
private String origin;
#Pattern(regexp ="^[A-Z]{3}" message ="Invalid IATA code")
private String destination;
#virendra chauhan: thank you for your inspiration.
I solved so:
#RequestMapping(value = "/crazy-air-response", method = RequestMethod.GET)
public List<CrazyAirResponse> getCrazyAirResponse(
#RequestParam("origin") #Pattern(regexp = "^[A-Z]{3}", message = "Invalid IATA code")
String origin,
#RequestParam("destination") #Pattern(regexp = "^[A-Z]{3}", message = "Invalid IATA code")
String destination,
#RequestParam("departureDate") String departureDate,
#RequestParam("returnDate") String returnDate,
#RequestParam("passengerCount") int passengerCount
) {
CrazyAirRequest crazyAirRequest = new CrazyAirRequest(origin, destination, departureDate, returnDate,
passengerCount);
return crazyAirService.getCrazyAirResponse(crazyAirRequest);
}

Multiple fields (custom + notnull) validation while using spring validation

I have a dto like this
#FieldMatch(first = "email", second = "emailConfirm", message = "E-posta adresleri eslesmeli")
public class EmailDTO {
private String email;
private String emailConfirm;
this is validator
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
private String firstFieldName;
private String secondFieldName;
private String message;
#Override
public void initialize(final FieldMatch constraintAnnotation) {
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
message = constraintAnnotation.message();
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
boolean valid = true;
try
{
final Object firstObj = BeanUtils.getProperty(value, firstFieldName);
final Object secondObj = BeanUtils.getProperty(value, secondFieldName);
valid = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
}
catch (final Exception ignore)
{
// ignore
}
if (!valid){
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode(firstFieldName)
.addConstraintViolation()
.disableDefaultConstraintViolation();
}
return valid;
}
}
this is interface
arget({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = FieldMatchValidator.class)
#Documented
public #interface FieldMatch {
String message() default "The fields must match";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String first();
String second();
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Documented
#interface List
{
FieldMatch[] value();
}
}
but i also want to change the class to this
#FieldMatch(first = "email", second = "emailConfirm", message = "E-posta adresleri eslesmeli")
public class EmailDTO {
#Max(value = 150, message = "E-posta karakter sayisi fazla!")
#Email(message = "Email should be valid")
#NotNull(message = "Name cannot be null")
#NotEmpty(message = "Name cannot be null")
private String email;
#Max(value = 150, message = "E-posta karakter sayisi fazla!")
#Email(message = "Email should be valid")
#NotNull(message = "Name cannot be null")
#NotEmpty(message = "Name cannot be null")
private String emailConfirm;
Should I use another generic constraint or a lot of annotations like shown above? I also have other entities like password, etc and those entities will also have same validations.
Some fields can be nullable, so not every field has to be checked by null constraint. I want to do a very generic validation. I will send to front end (thymeleaf), so I need to see which constraint is violated.
Email has #email annotation that wont be in the password validation. Others are common. like
notnull, notempty, matching, notblank
I was not able to find good examples. I found below posts on the topic, but I could not find an example of custom + for example #email validation together.
spring-custom-annotation-validation-with-multiple-field
how-can-i-validate-two-or-more-fields-in-combination
spring-mvc-custom-validator post shows validation for different fields.

Java 303 / 349 start date before end date validation

Is there a better way of writing a Java validator which ensures that a start date is before an end date than writing a class level ConstraintValidator in the following manner:
// VALIDATOR IMPLEMENTATION
public class StartBeforeEndDateValidator implements ConstraintValidator<StartBeforeEndDateValid, Object> {
// cannot use LocalDate here...
private String start;
private String end;
#Override
public void initialize(final StartBeforeEndDateValid annotation) {
start = annotation.start();
end = annotation.end();
}
#Override
public boolean isValid(final Object bean, final ConstraintValidatorContext context) {
try {
final String startDateStr = BeanUtils.getProperty(bean, start);
final String endDateStr = BeanUtils.getProperty(bean, end);
final LocalDate startDate = new LocalDate(startDateStr);
final LocalDate endDate = new LocalDate(endDateStr);
return !startDate.isAfter(endDate);
} catch (final Exception e) {
return false;
}
}
}
// USAGE
#StartBeforeEndDateValid(start = "startDate", end = "endDate")
#Entity
public class MyBean {
#NotNull
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate startDate;
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate endDate;
...
}
I don't really like the fact that I have to use reflection to extract the 2 date objects from the bean. Unfortunately afaik the validation spec does not specify a way to set only the values you want to validate from the bean.
One way would be to add an interface to MyBean
public interface StartEndDateable {
public LocalDate getStartDate();
public LocalDate getEndDate();
}
public class MyBean implements StartEndDatable {
...
Then you can set the generic type on ConstraintValidator to the new interface instead of Object.
public class StartBeforeEndDateValidator implements ConstraintValidator<StartBeforeEndDateValid, StartEndDatable> {
#Override
public void initialize(StartBeforeEndDateValid annotation) {
}
#Override
public boolean isValid(StartEndDatable bean, ConstraintValidatorContext context) {
final LocalDate startDate = bean.getStartDate();
final LocalDate endDate = bean.getEndDate();
return !startDate.isAfter(endDate);
}
}
Obviously any class you then want to validate with the start and end date will have to implement the StartEndDateable (Not the best name, I know, but I'm sure you can think of something better) and define the getStartDate and getEndDate methods.

Passing date as json object through Spring Hibernate

I am using Spring Hibernate framework. And I have a problem in passing date as json object. Whenever I try to insert an object, it says error 400, request syntactically incorrect.
My controller class
#RequestMapping(value="/hospital", method= RequestMethod.POST,
consumes=MediaType.APPLICATION_JSON_VALUE,produces=MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody Status addHospitalInfo(#RequestBody HospitalInformation hospitalInformation){
try{
if(hospitalService.addHospitalInfo(hospitalInformation)){
return new Status(1,"Success");
}else{
return new Status(0,"Failed");
}
}catch(Exception e){
e.printStackTrace();
return new Status(0,e.getMessage());
}
}
My domain class is
private Integer hospitalId;
private String shortName;
private String name;
private Integer packageId;
private Date implementationDate;
private Date validFrom;
private Date validUpTo;
public enum SubscriptionType{Free,Complimentary,Paid}
private Integer totalUsers;
private Package packages;
public enum Status{Active,Inactive}
private SubscriptionType subscriptionType;
private Status status;
//normal getters and setters for other fields
#Column(name = "implementation_date",
nullable = false)
public Date getImplementationDate() {
return implementationDate;
}
public void setImplementationDate(Date implementationDate)
{
this.implementationDate = implementationDate;
}
#Column(name = "valid_from",
nullable = false)
public Date getValidFrom() {
return validFrom;
}
public void setValidFrom(Date validFrom)
{
this.validFrom =validFrom;
}
#Column(name = "valid_upto",
nullable = false)
public Date getValidUpTo() {
return validUpTo;
}
public void setValidUpTo(Date validUpTo)
{
this.validUpTo =validUpTo;
}
My Dao is
#Transactional
public boolean addHospitalInfo(HospitalInformation hospitalInformation)
throws Exception {
Session session=sessionFactory.openSession();
Transaction tx=session.beginTransaction();
if(findByPackageId(hospitalInformation.getPackageId())== null){
return false;
}
else{
session.save(hospitalInformation);
tx.commit();
session.close();
return true;
}
}
#Transactional
public Package findByPackageId(Integer packageId) throws Exception {
Session session=sessionFactory.openSession();
Transaction tx= session.beginTransaction();
List<Package> package1= new ArrayList<Package>();
package1=session
.createQuery("from Package where packageId=?")
.setParameter(0, packageId)
.list();
if (package1.size() > 0) {
return package1.get(0);
} else {
return null;
}
}
And my service class just saves the object into database. So I need help on how to pass date as json object. Thankyou in advance.
To fix your issue you can do one of the two things:
Either use a format that Jackson already recognizes ("yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "EEE, dd MMM yyyy HH:mm:ss zzz", "yyyy-MM-dd")
or
write a custom deserializer e.g. for yyyyMMdd
public class YourDateDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
String date = jp.getText();
try {
return format.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
and annotate your date fields like
#JsonDeserialize(using = YourDateDeserializer.class)
private Date implementationDate;
#JsonDeserialize(using = YourDateDeserializer.class)
private Date validFrom;
#JsonDeserialize(using = YourDateDeserializer.class)
private Date validUpTo;
Serialization
To have your dates printed the way you want in your JSON response, you can write a custom JSON serializer and annotate the fileds with it, so for yyyyMMdd something like
public class YourDateSerializer extends JsonSerializer<Date> {
#Override
public void serialize(Date value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,JsonProcessingException {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
jgen.writeString(format.format(value));
}
#Override
public Class<Date> handledType() {
return Date.class;
}
}
and than annotate your field, e.g.
#JsonDeserialize(using = YourDateSerializer.class)
#JsonDeserialize(using = YourDateDeserializer.class)
private Date implementationDate;
Global config
Note also that you can configure your custom serializers to take effect globally, by customizing Jackson's ObjectMapper that is in charge for the conversion. So something like
#Component
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
SimpleModule module = new SimpleModule("JsonDateModule", new Version(2, 0, 0, null, null, null));
module.addSerializer(Date.class, new YourDateSerializer());
module.addDeserializer(Date.class, new YourDateDeserializer());
registerModule(module);
}
}
you would need to register your CustomObjectMapper with spring. If you're using the XML config, it would be something like
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="your.package.CustomObjectMapper"/>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

Categories

Resources