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

Deadly 提交于 2019-12-01 02:41:12

问题


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.


回答1:


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":"foo@bar.com"}' -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




回答2:


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?




回答3:


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




回答4:


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).



来源:https://stackoverflow.com/questions/45086048/bean-validation-with-jax-rs-rest-easy-parameter-name-not-recognized

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!