6

I have implemented a custom annotation @Password to perform validation on an argument of my method setPassword(). The annotation is defined like this:

// Password.java
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import javax.validation.*;

@Target({METHOD, FIELD, PARAMETER, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordValidator.class)
@Documented
public @interface Password {
    String message() default PasswordValidator.message;
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

And the current implementation of the validator is this:

// PasswordValidator.java
package utils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class PasswordValidator implements ConstraintValidator<Password, String> {

    /* Default error message */
    final static public String message = "error.invalid.password";

    /**
    * Validator init
    * Can be used to initialize the validation based on parameters
    * passed to the annotation.
    */
    public void initialize(Password constraintAnnotation) {
        System.out.println("in initialize()");
    }

    public boolean isValid(String string, ConstraintValidatorContext constraintValidatorContext) {
        System.out.println("in isValid()");
        return false;
    }
}

Note that in the current implementation isValid() always returns false. The reason will be apparent shortly.

My usage of the validator is in a class User. For brevity I won't post the whole source here, but the relevant parts are:

package models;

import utils.Password;
// other imports omitted


@Entity
@Table(name = "users", schema="public")
public class User {

    @Required
    private String password;

    ...

    public void setPassword(@Password String clearPassword) {
        try {
            this.password = HashHelper.createPassword(clearPassword);
        } catch (AppException e) {
            e.printStackTrace();
        }
    }

    ...
}

The basic idea is that I use the User class to store a hashed password for a user, but before setting the hashed password, I (would like to) run validation on the unhashed password (i.e. clearPassword).

The problem I am having is that this validation is not taking place. In the current implementation, it should (according to my understanding) always throw a ConstraintViolationException because isValid() always returns false, but this is not the case.

I have checked that the annotation is being attached to the method argument by calling (in another part of the application) something along the lines of:

Method method = user.getClass().getMethod("setPassword", new Class[] { String.class });
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
System.out.println(method.getName() + ": " + parameterAnnotations[0][0]);

which produces the following output:

setPassword:@utils.Password(message=error.invalid.password, payload=[], groups=[])

So this tells me the annotation is being applied to the method argument. But I can't understand why I'm not getting the ConstraintViolationException when I actually call the method. I also never see the output "in initialize()" or "in isValid()" that I added to these methods as a check to see if they're being fired.

As another test, I also added the @Password annotation to the member variable password in User.class. This causes the ConstraintViolationException to be thrown as expected, e.g. when I try to persist a User object.

Can anyone shed light as to why the annotation on the method argument is not working properly? Thanks in advance!

robguinness
  • 16,266
  • 14
  • 55
  • 65

2 Answers2

3

I think you are missing this declaration in your Password annotation:

ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;

From the docs:

validationAppliesTo is used at constraint declaration time to clarify what the constraint targets (i.e. the annotated element, the method return value or the method parameters).

The element validationAppliesTo must only be present for constraints that are both generic and cross-parameter, it is mandatory in this situation. A ConstraintDefinitionException is raised if these rules are violated.

The type of the validationAppliesTo parameter is ConstraintTarget. The default value must be ConstraintTarget.IMPLICIT.

Community
  • 1
  • 1
Danail Alexiev
  • 7,624
  • 3
  • 20
  • 28
  • Hi, I added this declaration, and now I get a rather confusing exception: Caused by: javax.validation.ConstraintDefinitionException: HV000159: Only constraints with generic as well as cross-parameter validators must define an attribute validationAppliesTo(), but constraint utils.Password does. Any ideas? – robguinness Aug 26 '15 at 07:12
  • From the looks of things, you have a generic and cross-platform constraint. In that case you must create two different validators - one for generic elements and another one, that supports parameter lookup. Check for an example annotation here - http://docs.jboss.org/hibernate/validator/5.0/reference/en-US/html/validator-customconstraints.html#example-dual-cross-parameter-constraint, and for an example parameter validator here - http://docs.jboss.org/hibernate/validator/5.0/reference/en-US/html/validator-customconstraints.html#example-cross-parameter-validator – Danail Alexiev Aug 26 '15 at 10:28
  • @robguinness did you ever figure out what the problem was? I have the exact same case as you, also for a password. – Sebastiaan van den Broek Mar 01 '18 at 17:49
0

It won't get validated because hibernate validator validates on user object not on the method setPassword. In case you want to throw that exception, you have to get the ExecutableValidator instance, call validateParameters method and then throw the ConstraintViolationException by yourself.

Lê văn Huy
  • 361
  • 3
  • 7