JSR 303 ConstraintViolationException and detailed message - java

I have configured my Spring Boot 1.5.1 application with JSR 303 validation. Right now I'm getting ConstraintViolationException in case of wrong data.
Is it possible to get a detailed message(now it is null) about what was wrong(for example what field or method parameter violates constraints) with ConstraintViolationException ?

Well, your ConstraintViolationException has a set of ConstraintViolation objects. Each of them has the details that you want.
public static <T> String getValidationMessage(ConstraintViolation<T> violation) {
String className = violation.getRootBeanClass().getSimpleName();
String property = violation.getPropertyPath().toString();
//Object invalidValue = violation.getInvalidValue();
String message = violation.getMessage();
return String.format("%s.%s %s", className, property, message);
}
With this I would get a message like, e.g "OrderLine.quantity must be greater than 0"

Related

exceptionOnErrorStatus in ClientConfiguration of Declarative Client not being applied in Micronaut version 3.7.3

I have provided a configuration class to my Declarative HttpClient, to force throwing an exception on error (status>400), or maybe I misunderstood the exceptionOnErrorStatus configuration option.
#Client(value = "/", configuration = ApiClientConfiguration)
interface Api {
#Get("/author/{id}")
AuthorResource getAuthor(Long id, #Header(name="Authorization") String authorization)
}
Here's the configuration class
#ConfigurationProperties("httpclient.api")
class ApiClientConfiguration extends DefaultHttpClientConfiguration {
#Override
boolean isExceptionOnErrorStatus() {
return true
}
}
My #MicronautTest is expected to throw an exception, but it doesn't:
def "It fails to get an non-existing Author"() {
given:
def token = viewer()
when:
//This commented code throws an exception as expected
/*
def author = client.toBlocking().exchange(HttpRequest.create(
HttpMethod.GET,
"/author/${badId}"
).bearerAuth(token))
*/
//This does not despite the provided HttpClientConfiguration
def author = api.getAuthor(badId, bearerAuth(token))
then:
def ex = thrown(HttpClientResponseException)
ex.status == NOT_FOUND
ex.getResponse().getBody(ErrorResource).map {
assert it.message == "Author with id ${badId} not found."
it
}.isPresent()
where:
badId = anyInt()
}
Any help appreciated. I am not used to configuration classes, so there may be a mistake in how I use it, the other possibility is that exceptionOnErrorStatus does not mean what I think it means. I have checked the documentation and I think it does, though.
It looks like the Configuration class is used, the method isExceptionOnErrorStatus() is called, but it is not interpreted as I thought it would.
Conclusion, when you are using a Declarative client and want to test the response when the status>400, make your interface return a HttpResponse<X>.
#Get("/author/{id}")
HttpResponse<AuthorResource> getAuthor(Long id, #Header(name = "Authorization") String authorization)
The behaviour is different from the low-level client, which throws an exception.
Modify your Test:
def response = api.getAuthor(badId, bearerAuth(token))
response.status == NOT_FOUND
response.getBody(ErrorResponse).map {
assert it.message == "Author with id ${badId} not found."
it
}.isPresent()

Override DropWizard ConstraintViolation message

So I want to change the validation messages used to validate a model through a DropWizard resource.
I'm using java bean validation annotations. For example here is one of the fields I want to validate:
#NotEmpty(message = "Password must not be empty.")
I can test this works as expected using a validator.
However when I use DropWizard to do the validation on the resource it adds some extra stuff to that message. What I see is this - password Password must not be empty. (was null) and I've found the code that does this here - https://github.com/dropwizard/dropwizard/blob/master/dropwizard-validation/src/main/java/io/dropwizard/validation/ConstraintViolations.java
Specifically this method -
public static <T> String format(ConstraintViolation<T> v) {
if (v.getConstraintDescriptor().getAnnotation() instanceof ValidationMethod) {
final ImmutableList<Path.Node> nodes = ImmutableList.copyOf(v.getPropertyPath());
final ImmutableList<Path.Node> usefulNodes = nodes.subList(0, nodes.size() - 1);
final String msg = v.getMessage().startsWith(".") ? "%s%s" : "%s %s";
return String.format(msg,
Joiner.on('.').join(usefulNodes),
v.getMessage()).trim();
} else {
return String.format("%s %s (was %s)",
v.getPropertyPath(),
v.getMessage(),
v.getInvalidValue());
}
}
Is there any way I can override this behaviour? I just want to display the message that I set in the annotation...
Here is a programmatic solution in dropwizard 0.8:
public void run(final MyConfiguration config, final Environment env) {
AbstractServerFactory sf = (AbstractServerFactory) config.getServerFactory();
// disable all default exception mappers
sf.setRegisterDefaultExceptionMappers(false);
// register your own ConstraintViolationException mapper
env.jersey().register(MyConstraintViolationExceptionMapper.class)
// restore other default exception mappers
env.jersey().register(new LoggingExceptionMapper<Throwable>() {});
env.jersey().register(new JsonProcessingExceptionMapper());
env.jersey().register(new EarlyEofExceptionMapper());
}
I think it's more reliable than a config file. And as you can see it also enables back all other default exception mappers.
ConstraintViolationExceptionMapper is the one which uses that method. In order to override it, you need to deregister it and register your own ExceptionMapper.
Remove the exception mapper(s)
Dropwizard 0.8
Add the following to your yaml file. Note that it will remove all the default exception mappers that dropwizard adds.
server:
registerDefaultExceptionMappers: false
Dropwizard 0.7.x
environment.jersey().getResourceConfig().getSingletons().removeIf(singleton -> singleton instanceof ConstraintViolationExceptionMapper);
Create and add your own exception mapper
public class ConstraintViolationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException exception) {
// get the violation errors and return the response you want.
}
}
and add your exception mapper in your application class.
public void run(T configuration, Environment environment) throws Exception {
environment.jersey().register(ConstraintViolationExceptionMapper.class);
}
#ValidationMethod should be useful here. isn't it?
http://www.dropwizard.io/0.9.0/docs/manual/validation.html
#ValidationMethod(message="Password cannot be empty")
#JsonIgnore
public boolean isPasswordProvided() {
return false if password not provided;
}

Bean Validation Messages from the Program

I have a bean with validation annotations. I am going to trigger the validation manually using the following code:
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, validationGroup);
My question is, 1.) how do you get the field that failed validation and 2.) How to get the associated message?
I do not want to use .properties file. There is no front-end to display. But its a service and I need to send response with failed validation message and field that failed validation.
How do you get the field that failed validation?
The field that failed validation will be returned in the MethodConstraintViolationException. You retrieve the individual violations by calling getConstraintViolations() and then the field can be retrieved by getPropertyPath() and walking the nodes.
However, if you have a case where the field name returned in the response does not match the name of the property in the bean, for example if you are returning snake case responses (i.e. user_name), but your bean property name is username, you have to get a little more creative.
In this scenario you can store the name of the field as a Payload on the bean validation annotation.
Response Field:
#JsonProperty("user_name")
#NotEmpty(message = ErrorMessageKeys.USERNAME_REQUIRED,
payload = {FieldNamePayload.UserName.class})
private String username;
Payload Class:
public class FieldNamePayload
{
/**
* Represents the field name "user_name"
*/
public static class UserName implements ValuePayload
{
private static final String value = "user_name";
#Override
public String getValue()
{
return value;
}
}
}
Retrieving the Payload in your Exception Mapper:
List<MethodConstraintViolation<?>> violations = new ArrayList<MethodConstraintViolation<?>>(exception.getConstraintViolations());
for(MethodConstraintViolation<?> violation : violations)
{
String field = getFieldName(violation);
String message = violation.getMessage();
for(Class<? extends Payload> payload : new ArrayList<Class<? extends Payload>>(violation.getConstraintDescriptor().getPayload()))
{
//Retrieve field name from constraint payload
if(payload.isAssignableFrom(FieldNamePayload.UserName.class))
{
field = getPayloadValue(payload);
}
}
//Create an error response here!
}
How do you get the associated message?
By default the bean validation framework looks up the messages in localized files at the root of the classpath with the following naming convention:
ValidationMessages.properties for the default locale.
ValidationMessages_{locale}.properties when localization is required
If you want to override this behavior and change where the validation messages are being retrieved from you can use hibernate's validation provider and implement a custom ResourceBundleLocator which you would add to the ResourceBundleMessageInterpolator.
ValidatorFactory validatorFactory = Validation
.byProvider(HibernateValidator.class)
.configure()
.messageInterpolator(
new ResourceBundleMessageInterpolator(
new MyCustomResourceBundleLocator()))
.buildValidatorFactory();
Regarding 1, what do you mean with field? The java.lang.reflect.Field? You don't get access to it directly. What you can do is to call ConstraintViolation#getPropertyPath to get the property path. You need to iterate the nodes and at the leaf node you can call for example getName() to get the property name and getKind() to determine the type of the node.
Regarding 2, ConstraintViolation#getMessage() gives you the interpolated error message
I am not sure what you mean when you say that you don't want to use a properties file. Bean Validation will per default read the error messages from properties files (either the built-in ones, or the ones you add to your application).

Using Hibernate (JSR 303) method validation to compare parameters

I'm using JSR303 method validator extensivly to validate the inputs to my service layer automaticaly with a little help fron aspectj. One thing that is surely missing is the ability to do cross parameter validation, i.e. for example compare two date parameters. How can I achieve that with hibernate method validation ? Is it possible ? Any other recommended way to address this ?
This is the current code of my aspect
public abstract aspect ValidationAspect {
#Inject
private Validator validator;
protected ParameterValidationError[] validateParameters(
JoinPoint jp) {
MethodSignature methodSignature = (MethodSignature)jp.getSignature();
Method targetMethod = methodSignature.getMethod();
Object targetObj = jp.getThis();
Object[] args = jp.getArgs();
String[] names = ((CodeSignature)jp.getSignature()).getParameterNames();
MethodValidator methodValidador = validator.unwrap(MethodValidator.class);
Set<? extends MethodConstraintViolation<?>> validationErrors = methodValidador.validateAllParameters(
targetObj,
targetMethod,
args);
ParameterValidationError[] output = new ParameterValidationError[validationErrors.size()];
int idx = 0;
for (MethodConstraintViolation<?> mcv : validationErrors ) {
output[idx++] = new ParameterValidationError(
mcv.getParameterIndex(),
names[mcv.getParameterIndex()],
mcv.getInvalidValue(),
mcv.getMessage());
}
return output;
}
}
Hibernate Validator's method level validation does not allow cross parameter validation. It implements method validation as specified in the appendix C of Bean Validation 1.0.
Part of the ongoing discussion for Bean Validation 1.1 is whether and how to support this feature. See also
http://beanvalidation.org/proposals/BVAL-241/#cross_parameter
https://hibernate.onjira.com/browse/BVAL-232

Generating a error code per property in Hibernate Validator

I am looking at using Hibernate Validator for a requirement of mine. I want to validate a JavaBean where properties may have multiple validation checks. For example:
class MyValidationBean
{
#NotNull
#Length( min = 5, max = 10 )
private String myProperty;
}
But if this property fails validation I want a specific error code to be associated with the ConstraintViolation, regardless of whether it failed because of #Required or #Length, although I would like to preserve the error message.
class MyValidationBean
{
#NotNull
#Length( min = 5, max = 10 )
#ErrorCode( "1234" )
private String myProperty;
}
Something like the above would be good but it doesn't have to be structured exactly like that. I can't see a way to do this with Hibernate Validator. Is it possible?
You could create a custom annotation to get the behaviour you are looking for and then on validating and using refelection you could extract the value of the annotation. Something like the following:
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ErrorCode {
String value();
}
In your bean:
#NotNull
#Length( min = 5, max = 10 )
#ErrorCode("1234")
public String myProperty;
On validating your bean:
Set<ConstraintViolation<MyValidationBean>> constraintViolations = validator.validate(myValidationBean);
for (ConstraintViolation<MyValidationBean>cv: constraintViolations) {
ErrorCode errorCode = cv.getRootBeanClass().getField(cv.getPropertyPath().toString()).getAnnotation(ErrorCode.class);
System.out.println("ErrorCode:" + errorCode.value());
}
Having said that I probably would question the requirements for wanting error codes for these types of messages.
From the section 4.2. ConstraintViolation of the specification:
The getMessageTemplate method returns the non-interpolated error message (usually the message attribute on the constraint declaration). Frameworks can use this as an error code key.
I think this is your best option.
What I would try to do is isolate this behavior on the DAO Layer of the application.
Using your example we would have:
public class MyValidationBeanDAO {
public void persist(MyValidationBean element) throws DAOException{
Set<ConstraintViolation> constraintViolations = validator.validate(element);
if(!constraintViolations.isEmpty()){
throw new DAOException("1234", contraintViolations);
}
// it's ok, just persist it
session.saveOrUpdate(element);
}
}
And the following exception class:
public class DAOException extends Exception {
private final String errorCode;
private final Set<ConstraintViolation> constraintViolations;
public DAOException(String errorCode, Set<ConstraintViolation> constraintViolations){
super(String.format("Errorcode %s", errorCode));
this.errorCode = errorCode;
this.constraintViolations = constraintViolations;
}
// getters for properties here
}
You could add some annotation information based on what property has not validated from here, but always doing this on the DAO method.
I hope this helped.

Categories