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).
Related
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"
I have the following annotation
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = IdValidator.class)
public #interface Id {
String message() default "{Id field cannot be null}";
}
that I'm using on my class
public class Person {
#Id
String firstName;
#Id
String lastName;
}
When I call
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Set<ConstraintViolation<FakePerson>> violations = validator.validate(person);
I want a ConstrainstViolation to have a message that says "firstName cannot be null". How can I do that?
Is there a way to create the messages associate with the path inside the ConstraintValidator?
public class IdValidator implements ConstraintValidator<Id, Object> {
#Override
public boolean isValid(Object value, ConstraintValidatorContext context)
{
//Ideally I can build the messages associate with the property here with ConstraintValidatorContext
}
}
There are a couple of options.
The first would be to add a new attribute to your annotation, shown below is label.
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = PKFieldValidator.class)
public #interface Id {
String message() default "{Id field cannot be null}";
String label();
}
Then in the class, you'd use
#Id(label="firstName")
private String firstName;
#Id(label="lastName")
private String lastName;
Then simply make your message be something like
Id.message={label} is required.
But the problem with that approach is that you're dropping in bean field names into translations that could be in other languages. How much sense does Se requiere el firstName make in Spanish?
My preferred suggestion is to actually keep bean validation messages generic. Things such as Field is required or Must be greater than 0 seem very reasonable to me and allows for reuse.
For situations where you need to associate these violation messages with the properties, simply iterate the violations, get the propertyPath and print the path plus the message to your logs or whatnot.
From web frameworks, I prefer to do precisely that, iterate the constraint violations, adding them to a Map<String,String> where the key is the propertyPath and the value is the violation message. Then in the UI, I associate the message next to each of the field's input controls, keeping things clean and concise all the while easy for the user to understand and read.
I am sending the following JSON object to my Java Spring application
{
"ewsUrl":"https://dummy.url.com",
"ewsIdentityToken":"12345",
"itemIds":["itemids"],
"entityId":null,
"documentType":"Dummy",
"documentStatus":"Record",
"filename":"message.eml",
"metadata":{"title":"message"}
}
I have defined an object public class RequestObject and in my controller I have
public RequestObject
testMyStuff(#CurrentUser User currentUser,
#RequestBody RequestObject myDummyObject) throws Exception {
return myDummyObject
}
My application returns the error Could not read document: Root name 'ewsUrl' does not match expected ('RequestObject') for type...etc
However if I send the JSON formatted like this it successfully maps the object:
{ "RequestObject":
{
"ewsUrl":"https://dummy.url.com",
"ewsIdentityToken":"12345",
"itemIds":["itemids"],
"entityId":null,
"documentType":"Dummy",
"documentStatus":"Record",
"filename":"message.eml",
"metadata":{"title":"message"}
}
}
I do not want to name the object in my JSON, I want to send as described in the first example. How do I achieve this?
UPDATE:
RequestObject.java
public class RequestObject {
public String ewsUrl;
public String ewsIdentityToken;
public String[] itemIds;
public String entityId;
public String documentType;
public String documentStatus;
public String filename;
public Metadata metadata;
public RequestObject() {
}
public static class Metadata {
public String title;
}
}
UPDATE2:
The way it is described in this example suggests that the object does not need to be named in the JSON data in the POST request. I think I am emulating this example, but I'm getting different results.
Is there a configuration for Jackson/Spring that I am missing?
Update 3:
The complete error message is:
Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException:
Could not read document:
Root name 'ewsUrl' does not match expected ('RequestObject') for type
[simple type, class uk.co.test.RequestObject] at [Source:
java.io.PushbackInputStream#7e223182; line: 2, column: 9];
nested exception is com.fasterxml.jackson.databind.JsonMappingException:
Root name 'ewsUrl' does not match expected ('RequestObject') for type
[simple type, class uk.co.test.RequestObject]
There's some configuration settings that look like they can be defined for the ObjectMapper that controls the behaviour of the root element:
https://fasterxml.github.io/jackson-databind/javadoc/2.0.0/com/fasterxml/jackson/databind/DeserializationFeature.html#UNWRAP_ROOT_VALUE - see SerializationFeature.html#WRAP_ROOT_VALUE as well.
UNWRAP_ROOT_MODULE is disabled by default according to the docs so not sure why you're seeing the behaviour you are.
Config example for spring is available at
http://docs.spring.io/spring-framework/docs/3.2.3.RELEASE/javadoc-api/org/springframework/http/converter/json/JacksonObjectMapperFactoryBean.html
Just use JSONArray instead of JSONObject
Update
You can get your Json Object via JSONArray.getJSONObject()
Good evening, I'm trying to use Hibernate Validator, in the following scenario:
public class Car {
#NotNull
private String manufacturer;
#NotNull
#Size(min = 2, max = 14)
private String licensePlate;
#Min(2)
private int seatCount;
//setters and getters....
}
and I am trying to validate its attributes as follows:
public class CarMain {
public static Validator validator;
public static void main(String[] args) {
ValidatorFactory factory = Validation. buildDefaultValidatorFactory() ;
validator = factory. getValidator();
Car car = new Car(null,null,0);
Set<ConstraintViolation<Car>> st= validator.validate(car);
while(st.iterator.hasNext()){
ConstraintViolation<Car> cv = st.iterator.next();
System.out.println("Value: ("+cv.getInvalidValue()+") -->"+cv.getMessage());
System.out.println("Attribute: "+cv.getPropertyPath());
}
}
Here the whole entity is validated and the invalid values with the validation message and property path are displayed.
My question is:"Is it possible to validate only one attribute at a time with Hibernate Validator? Like I don't have to work with the whole object to validate it.
The Validator interface defines also a [Validator.validateProperty][1] method where you explicitly specify the property to validate. Mind you, you still need the object instance and you need to know the property you want to validate. This method is for example used by the integration of Bean Validation into JSF. Whether it makes sense to use it inm your case, will depend on your use case? Why don't you want to validate the whole object?
BTW, there is also Validator.validateValue which does not require an actual bean instance.
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.