Bean Validation with JAX-RS (rest-easy): parameter name not recognized
Asked Answered
K

4

8

I'm using JAX-RS resources with Bean Validation and integration between these two works as expected.

However, the default error messages generated in case of a validation error report parameter names as arg0, like so

[PARAMETER]
[login.arg0.password]
[password is required]
[]

Corresponding method definition:

@POST //and other JAX-RS annotations
public Response login(
        @NotNull
        @Valid
        LoginBody loginBody) {

   [...]

protected static class LoginBody {

    @NotNull(message =  EMAIL_REQUIRED)
    public String email;

    @NotNull(message = PASSWORD_REQUIRED)
    public String password;
}

While I'm generally fine with this message pattern, what actually is annyoing, is the fact that the original parameter name is not recognized, i. e. I'd rather like to see

login.loginBody.password instead of arg0.

Is there an easy way to fix this, e. g. somehow provide an explicit name for that parameter?

I'm using WildFly Swarm 2017.6.0. From what I found out this means I have resteasy + resteasy-validator + hibernate-validator

Thanks.

Kilauea answered 13/7, 2017 at 16:11 Comment(0)
G
2

You could try to compile your app with -parameters or instruct your IDE to do so, e.g. in case of eclipse: preferences -> java -> compiler -> "store information about method parameters (usable via reflection)"

With that in place you then need to instruct the Bean Validation infrastructure (e.g. ) hibernate-validator to use the ReflectiveParameterNamer via META-INF/validation.xml.

<parameter-name-provider>org.hibernate.validator.parameternameprovider.ReflectionParameterNameProvider</parameter-name-provider>

See also Hibernate Validator Configuration

I got something reliably working with the Paranamer library

META-INF/validation.xml:

<?xml version="1.0" encoding="UTF-8"?>
<validation-config
   xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
                    http://jboss.org/xml/ns/javax/validation/configuration
                    validation-configuration-1.1.xsd"
   version="1.1">
   <default-provider>org.hibernate.validator.HibernateValidator
   </default-provider>
   <message-interpolator>org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator
   </message-interpolator>
   <traversable-resolver>org.hibernate.validator.internal.engine.resolver.DefaultTraversableResolver
   </traversable-resolver>
   <constraint-validator-factory>org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl
   </constraint-validator-factory>
   <parameter-name-provider>org.hibernate.validator.parameternameprovider.ParanamerParameterNameProvider</parameter-name-provider>
</validation-config>

To get paranamer working with wildfly I needed to create a parameter-namer jboss-module and reference that module from the module.xml of the hibernate-validator module.

With that in place I could simply write:

@POST
public Response login(@NotNull @Valid @Named("authRequest") AuthRequest authRequest) {
    return Response.ok().build();
}
...

public class AuthRequest {

    @NotNull(message = AuthMessages.EMAIL_REQUIRED)
    public String email;

    @NotNull(message = AuthMessages.PASSWORD_REQUIRED)
    public String password;
}

which yields the following response for a request sent via curl:

curl -H "Content-Type: application/json" -H "Accept: application/json" -d '{"email":"[email protected]"}' -v http://localhost:8080/javaweb-training/resources/auth

Response:

{"exception":null,"fieldViolations":[],"propertyViolations":[],"classViolations":[],"parameterViolations":[{"constraintType":"PARAMETER","path":"login.authRequest.password","message":"password.required","value":""}],"returnValueViolations":[]}%

... note login.authRequest.password instead of just login.arg0.password

Gastineau answered 18/7, 2017 at 10:19 Comment(2)
This works for me, thx a lot :) for reference: I added org.wildfly.swarm:jaxrs-validator as swarm-fraction and it worked out of the of box with just the declaration in the validation.xml.Kilauea
thx, compileJava.options.compilerArgs << "-parameters" did it for meGodfearing
O
2

There is a very simple solution: you can set your own error message in the constraint definition as follows

@NotNull(message = "password is required")

If you want a more generic solution based on the JAX-RS parameter annotations you can implement your own simple ParameterNamProvider and register it in validation.xml as follows. This has the advantage of not having to change the jboss module structure. I also didn't have to change any compiler flags...

public class AnnotatedParameterNameProvider implements ParameterNameProvider {
  @Override
  public List<String> getParameterNames(Constructor<?> constructor) {
    return lookupParameterNames(constructor.getParameterAnnotations());
  }

  @Override
  public List<String> getParameterNames(Method method) {
    return lookupParameterNames(method.getParameterAnnotations());
  }

  private List<String> lookupParameterNames(Annotation[][] annotations) {
    final List<String> names = new ArrayList<>();
    if (annotations != null) {
      for (Annotation[] annotation : annotations) {
        String annotationValue = null;
        for (Annotation ann : annotation) {
          annotationValue = getAnnotationValue(ann);
          if (annotationValue != null) {
            break;
          }
        }

        // if no matching annotation, must be the request body
        if (annotationValue == null) {
          annotationValue = "requestBody";
        }
        names.add(annotationValue);
      }
    }

    return names;
  }

  private static String getAnnotationValue(Annotation annotation) {
    if (annotation instanceof HeaderParam) {
      return ((HeaderParam) annotation).value();
    } else if (annotation instanceof PathParam) {
      return ((PathParam) annotation).value();
    } else if (annotation instanceof QueryParam) {
      return ((QueryParam) annotation).value();
    }
    return null;
  }
}

In validation.xml:

    <validation-config xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration validation-configuration-1.1.xsd"
                   version="1.1">
  <parameter-name-provider>com.yourcompany.providers.AnnotatedParameterNameProvider</parameter-name-provider>
</validation-config>

Note that you can also customize how the error message is formatted by implementing your own MessageInterpolator and registering it in the validation.xml

Okapi answered 20/6, 2018 at 12:44 Comment(0)
E
0

Can you try to implement an exception mapper for ConstraintViolationExceptions and see if the information you have there (the list of constraint violations) can help you to obtain the parameter name?

Exteriorize answered 16/7, 2017 at 8:59 Comment(1)
As Thomas' answer indicated, the main problem was that there has been no runtime information on the orginal parameters name wihtout the explicit compiler flag.Kilauea
O
0

Updated version of @thomas-darimont for Hibernate Validator 6.X.

Variant#1 - with build in Java 8 (using -parameters compile parameter)

  1. Specify dependencies (gradle example):
// Define explicit hibernate validator 6.x
implementation('org.hibernate.validator:hibernate-validator:6.0.13.Final')
implementation('org.jboss.resteasy:resteasy-validator-provider-11:3.6.2.Final') {
    // Exclude transitive hibernate validator 5.x
    exclude group: 'org.hibernate', module: 'hibernate-validator'
}
  1. Specify validator(s):
@GET
@Path("user/{userId}")
public Response getUser(@Size(min = 2) @PathParam("userId") String userId) {
    return null;
}

Note: org.hibernate.validator.internal.engine.DefaultParameterNameProvider will return parameter names obtained from the Java reflection API.


Variant #2 - use ParaNamer library. (xml configuration) In case you don't want to be dependant on compilation flag.

  1. Specify dependencies (gradle example):
// Define explicit hibernate validator 6.x
implementation('org.hibernate.validator:hibernate-validator:6.0.13.Final')
implementation('org.jboss.resteasy:resteasy-validator-provider-11:3.6.2.Final') {
    // Exclude transitive hibernate validator 5.x
    exclude group: 'org.hibernate', module: 'hibernate-validator'
}
// ParaNamer library
implementation('com.thoughtworks.paranamer:paranamer:2.8')
  1. Specify validator(s):
@GET
@Path("user/{userId}")
public Response getUser(@Size(min = 2) @PathParam("userId") String userId) {
    return null;
}
  1. Put <project_dir>/src/main/resources/META-INF/validation.xml
<?xml version="1.0" encoding="UTF-8"?>
<validation-config
        xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration
            http://xmlns.jcp.org/xml/ns/validation/configuration/validation-configuration-2.0.xsd"
        version="2.0">
    <parameter-name-provider>org.hibernate.validator.parameternameprovider.ParanamerParameterNameProvider</parameter-name-provider>
</validation-config>

Note: Since Hibernate Validator 6.x org.hibernate.validator.parameternameprovider.ReflectionParameterNameProvider is deprecated, use org.hibernate.validator.parameternameprovider.ParanamerParameterNameProvider instead.

Question: Can I configure this with Java-code style only? Unfortunately, no. (See details here).

Opprobrium answered 16/11, 2018 at 15:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.