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 ) { ... }
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With