Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I change the property path in a ConstraintValidator for Method arguments?

Tags:

If you are familiar with the Bean Validation Framework you know that you cannot get the name of a method argument. So if you do a @NotNull constraint on the first argument of a method and the validation fails the getPropertyPath will be something like "arg1".

I would like to create my own version of @NotNull that can take a value e.g. @NamedNotNull( "emailAddress" ). But I can't figure out how to override the #getPropertyPath in my Validator? Is there any way to do this or am I stuck with "arg1" or "arg2", etc.

EDIT

Based on the answer I received I was able to come up with the following implementation that allows me to take the value from my @QueryParam or @PathParam annotations and use those as the property path for Bean Validation annotations like @NotNull.

For Jersey you need to create the following class. Note the implementation of DefaultParameterNameProvider:

public class ValidationConfigurationContextResolver implements ContextResolver<ValidationConfig> {     @Override     public ValidationConfig getContext( final Class<?> type ) {         final ValidationConfig config = new ValidationConfig();         config.parameterNameProvider( new RestAnnotationParameterNameProvider() );         return config;     }      static class RestAnnotationParameterNameProvider extends DefaultParameterNameProvider {          @Override         public List<String> getParameterNames( Method method ) {             Annotation[][] annotationsByParam = method.getParameterAnnotations();             List<String> names = new ArrayList<>( annotationsByParam.length );             for ( Annotation[] annotations : annotationsByParam ) {                 String name = getParamName( annotations );                 if ( name == null )                     name = "arg" + ( names.size() + 1 );                  names.add( name );             }              return names;         }          private static String getParamName( Annotation[] annotations ) {             for ( Annotation annotation : annotations ) {                 if ( annotation.annotationType() == QueryParam.class ) {                     return QueryParam.class.cast( annotation ).value();                 }                 else if ( annotation.annotationType() == PathParam.class ) {                     return PathParam.class.cast( annotation ).value();                 }             }              return null;         }     } } 

Then in your RestConfig you need to add the following line:

register( ValidationConfigurationContextResolver.class ); 

That's it. Now your ConstraintValidationExceptions will contain the name of the QueryParam or PathParam they are annotated with. For example:

 public void getUser(       @NotNull @QueryParam( "emailAddress" ) String emailAddress,      @NotNull @QueryParam( "password" ) String password )   { ... } 
like image 551
robert_difalco Avatar asked Mar 19 '14 04:03

robert_difalco


2 Answers

If you are familiar with the Bean Validation Framework you know that you cannot get the name of a method argument

That's not quite correct. Bean Validation specifies the concept of a ParameterNameProvider which allows you to provide your own implementation. Hibernate Validator integrates with ParaNamer to provide parameter names. See the Validator online docs for more information. Once Validator supports Java 8, it will also support the Java 8 parameter naming facility.

IMO, you should give ParaNamer a go.

like image 164
Hardy Avatar answered Sep 18 '22 08:09

Hardy


Bean Validation 1.1 introduced the ParameterNameProvider interface for providing names for method and constructor parameters when creating constraint violation objects.


Hibernate Validator 5.2 introduced the ReflectionParameterNameProvider class, a ParameterNameProvider implementation that uses reflection to get the actual parameter names (to work properly, it requires the classes to be compiled with the -parameters compiler argument):

/**  * Uses Java 8 reflection to get the parameter names.  * <p>  * <p>For this provider to return the actual parameter names, classes must be compiled with the '-parameters' compiler  * argument. Otherwise, the JDK will return synthetic names in the form {@code arg0}, {@code arg1}, etc.</p>  * <p>  * <p>See also <a href="http://openjdk.java.net/jeps/118">JEP 118</a></p>  *  * @author Khalid Alqinyah  * @since 5.2  */ public class ReflectionParameterNameProvider implements ParameterNameProvider {      @Override     public List<String> getParameterNames(Constructor<?> constructor) {         return getParameterNames(constructor.getParameters());     }      @Override     public List<String> getParameterNames(Method method) {         return getParameterNames(method.getParameters());     }      private List<String> getParameterNames(Parameter[] parameters) {          List<String> parameterNames = newArrayList();         for (Parameter parameter : parameters) {             // If '-parameters' is used at compile time, actual names will be returned. Otherwise, it will be arg0, arg1...             parameterNames.add(parameter.getName());         }          return parameterNames;     } } 

Dropwizard extends it and add support to JAX-RS @XxxParam annotations with the JerseyParameterNameProvider that should work with other JAX-RS implementations too:

/**  * Adds jersey support to parameter name discovery in hibernate validator.  * <p>  * <p>This provider will behave like the hibernate-provided {@link ReflectionParameterNameProvider} except when a  * method parameter is annotated with a jersey parameter annotation, like {@link QueryParam}. If a jersey parameter  * annotation is present the value of the annotation is used as the parameter name.</p>  */ public class JerseyParameterNameProvider extends ReflectionParameterNameProvider {      @Override     public List<String> getParameterNames(Method method) {         Parameter[] parameters = method.getParameters();         Annotation[][] parameterAnnotations = method.getParameterAnnotations();         List<String> names = new ArrayList<>(parameterAnnotations.length);         for (int i = 0; i < parameterAnnotations.length; i++) {             Annotation[] annotations = parameterAnnotations[i];             String name = getParameterNameFromAnnotations(annotations).orElse(parameters[i].getName());             names.add(name);         }         return names;     }      /**      * Derives member's name and type from it's annotations      */     public static Optional<String> getParameterNameFromAnnotations(Annotation[] memberAnnotations) {          for (Annotation a : memberAnnotations) {             if (a instanceof QueryParam) {                 return Optional.of("query param " + ((QueryParam) a).value());             } else if (a instanceof PathParam) {                 return Optional.of("path param " + ((PathParam) a).value());             } else if (a instanceof HeaderParam) {                 return Optional.of("header " + ((HeaderParam) a).value());             } else if (a instanceof CookieParam) {                 return Optional.of("cookie " + ((CookieParam) a).value());             } else if (a instanceof FormParam) {                 return Optional.of("form field " + ((FormParam) a).value());             } else if (a instanceof Context) {                 return Optional.of("context");             } else if (a instanceof MatrixParam) {                 return Optional.of("matrix param " + ((MatrixParam) a).value());             }         }          return Optional.empty();     } } 

If you don't use Dropwizard, you can use the above code to create your own implementation.


Customization of the Validator used in validation of Jersey resource classes/methods can be done using ValidationConfig class and exposing it via ContextResolver<T> mechanism:

public class ValidationConfigurationContextResolver          implements ContextResolver<ValidationConfig> {      @Override     public ValidationConfig getContext(final Class<?> type) {         ValidationConfig config = new ValidationConfig();         config.parameterNameProvider(new CustomParameterNameProvider());         return config;     } } 

Then register the ValidationConfigurationContextResolver in ResourceConfig.

Refer to the Jersey documentation about Bean Validation support for more details.

like image 29
cassiomolin Avatar answered Sep 21 '22 08:09

cassiomolin