Hibernate Books

All times are UTC - 5 hours [ DST ]



Post new topic Reply to topic  [ 2 posts ] 
Author Message
 Post subject: An annotation to reference other object annotations...
PostPosted: Thu Jul 16, 2015 2:38 pm 
Newbie

Joined: Thu May 21, 2015 11:19 am
Posts: 7
Hello,
In large code bases, there may be times where developers use a Facade pattern to get a value from a different object. For example in the UserBean object below, getting the email actually comes from the User class. I'd like to add a @ValidateReference annotation (or something like it) to the Spec that references to the object and field for validation. In the current implementation, I have to validate twice using validator.validateValue(User.class, "email", email, EmailGroup.class) to validate the email is formatted correctly and then validator.validate(userBean) to validate that both fields match. I would prefer to validate once on the email property using validator.validate(userBean, EmailGroup.class) -- with the @ValidateReference addition. I could simply copy and paste the email constraints from User to UserBean but then I'd get complaints of not being DRY (Don't Repeat Yourself).

Please correct me if my understanding above is incorrect.

Code:
public class User
{
   private String name;
   private String email;

   @Size(min = 3, message = "Name should at least be 3 characters long")
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }

   @Pattern(regexp = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", message = "This is not a valid email", groups = EmailGroup.class)
   public String getEmail() {
      return email;
   }
   public void setEmail(String email) {
      this.email = email;
   }
}


Code:
@FieldsValueMatch(first="user.email", second = "confirmEmail", groups= EmailGroup.class)
public class UserBean {

    private User user = new User(); // more likely injected in a web form.
    private String confirmEmail;

   
    public String getName() {
        return user.getName();
    }

    public void setName(String name) {
        this.user.setName( name);
    }

    @ValidateReference(refClass=User.class method="email" groups=EmailGroup.class)
    public String getEmail() {
        return user.getEmail();
    }
    public void setEmail(String email) {
        this.user.setEmail(email);
    }

    public String getConfirmEmail()
    {
        return confirmEmail;
    }
    public void setConfirmEmail(String email)
    {
        this.confirmEmail = email;
    }
}


Code:
package com.networkfleet.constraints;


import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;


import com.networkfleet.constraints.FieldsValueMatchValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;

/**
* Validation annotation to validate 2 fields that have the same value.
* An array of fields and their matching confirmation fields can be supplied.
*
* Example, compare email fields in an HTML form:
* @ValueMatch(first = "password", second = "confirmPassword", message = "The password fields must match")
*
* Example, compare more than 1 pair of fields:
* @ValueMatch.List({
*   @ValueMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
*   @ValueMatch(first = "email", second = "confirmEmail", message = "The email fields must match")})
*/
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = FieldsValueMatchValidator.class)
@Documented
public @interface FieldsValueMatch
{
   String message() default "Fields don't match";


   Class<?>[] groups() default {};
   boolean ignoreCase() default false;

   Class<? extends Payload>[] payload() default {};

   /**
    * @return The first field
    */
   String first();

   /**
    * @return The second field
    */
   String second();

   /**
    * Defines several <code>@FieldMatch</code> annotations on the same element
    *
    * @see FieldsValueMatch
    */
   @Target({TYPE, ANNOTATION_TYPE})
   @Retention(RUNTIME)
   @Documented
   @interface List
   {
      FieldsValueMatch[] value();
   }
}


Code:
package com.networkfleet.constraints;

import org.apache.commons.beanutils.BeanUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class FieldsValueMatchValidator implements ConstraintValidator<FieldsValueMatch, Object>
{
   private String firstFieldName;
   private String secondFieldName;
   private boolean ignoreCase;

   @Override
   public void initialize(final FieldsValueMatch constraintAnnotation)
   {
      firstFieldName = constraintAnnotation.first();
      secondFieldName = constraintAnnotation.second();
      ignoreCase = constraintAnnotation.ignoreCase();
   }

   @Override
   public boolean isValid(final Object value, final ConstraintValidatorContext context)
   {
      try
      {
         final String firstObj = BeanUtils.getProperty(value, firstFieldName);
         final String secondObj = BeanUtils.getProperty(value, secondFieldName);

         if (firstObj == null)
         {
            return secondObj == null;
         }

         return (ignoreCase)?firstObj.equalsIgnoreCase(secondObj):firstObj.equals(secondObj);
      }
      catch (final Exception exception)
      {
         throw new IllegalArgumentException("Could not compare field("+firstFieldName+") with other field ("+secondFieldName+") because one (or more) of the field name(s) is invalid.", exception);
      }
   }
}




Top
 Profile  
 
 Post subject: Re: An annotation to reference other object annotations...
PostPosted: Tue Jul 21, 2015 3:36 am 
Hibernate Team
Hibernate Team

Joined: Sat Jan 24, 2009 12:46 pm
Posts: 388
Hi,

Could you open an issue at the BV spec tracker (https://hibernate.atlassian.net/browse/BVAL) so the feature can be discussed for inclusion in a future BV revision?

One way you could solve your problem already today would be to query the constraint metadata (see Validator#getConstraintsForClass()) of one model and use that to drive dynamic creation of equivalent constraints on the other model using the Hibernate Validator API for programmatic constraint declaration (see http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html_single/#section-programmatic-api).

Hth,

--GUnnar

_________________
Visit my blog at http://musingsofaprogrammingaddict.blogspot.com/


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 2 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.