Hibernate Error with Custom Bean Validation - java

I'm trying to create a custom bean validation, so I write this custom constraint:
#Documented
#Constraint(validatedBy = ValidPackageSizeValidator.class)
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidPackageSize {
String message() default "{br.com.barracuda.constraints.ValidPackageSize}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And a validator:
public class ValidPackageSizeValidator implements ConstraintValidator<ValidPackageSize, PackageSize> {
...
#Override
public boolean isValid(PackageSize value, ConstraintValidatorContext context) {
...validation login here..
}
}
Also, I wanted the validation to be performed on the service layer just after some decorators are called, so I created an another decorator to handle this task..
#Decorator
public abstract class ConstraintsViolationHandlerDecorator<T extends AbstractBaseEntity> implements CrudService<T> {
#Any
#Inject
#Delegate
CrudService<T> delegate;
#Inject
Validator validator;
#Override
#Transactional
public T save(T entity) {
triggerValidations(entity);
return delegate.save(entity);
}
private void triggerValidations(T entity) {
List<String> errorMessages = validator.validate(entity).stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
if (!errorMessages.isEmpty()) {
throw new AppConstraintViolationException(errorMessages);
}
}
}
Everything works, but if validations pass, hibernate throws an error:
ERROR [default task-6] (AssertionFailure.java:50) - HHH000099: an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: null id in br.com.barracuda.model.entities.impl.PackageSize entry (don't flush the Session after an exception occurs)
My entities use auto-generated id values.
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
Using Widlfly 9 with JEE 7.

Validation was being executed twice, once in the service layer (where I wanted it to happen) and once when entity was persisted/merged (jpa was calling it). So I disabled it by adding this line to my persistence.xml:
<property name="javax.persistence.validation.mode" value="none"/>
Now everything works fine

Related

Spring Data recursive validation

Does this type of validation work?
Annotation:
#Constraint(validatedBy = UniqueEmailValidator.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD})
public #interface UniqueEmail {
public String message() default "Error message";
public Class<?>[] groups() default {};
public Class<? extends Payload>[] payload() default {};
}
Validator:
#Component
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
#Autowired
private UserRepository userRepository;
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return userService.isEmailUnique(value); // read as a call to userRepository.findByEmail(emailAddress)
}
}
And the entity
#Entity
public class User {
...
#UniqueEmail
private String email;
}
It fails because of the recursive calls between isValid() method and userRepository.findByEmail(). It this correct behavior? Does the findByEmail always create a new User and apply the validation on it?
update:
A part of the stacktrace:
java.lang.StackOverflowError: null
...
(many times)
...
UserService.isEmailUnique(UserService.java:84)
...
UniqueEmailValidator.isValid(UniqueEmailValidator.java:29)
UniqueEmailValidator.isValid(UniqueEmailValidator.java:13)
The property spring.jpa.properties.javax.persistence.validation.mode=none resolves this. But still it was not even a double validation.
Answering on my own question.
Thanks for this hint:
In validation, you are doing a query on the same entity which means before doing the query, hibernate need to flush what is queued in your session.
In other words if I got it right, in this case hibernate saves before the validation with it's query.
So I've added the same propagation for my method:
#Transactional(propagation = Propagation.NOT_SUPPORTED)
Optional<User> findByEmail(String email);
It works.
Also I don't need to keep the spring.jpa.properties.javax.persistence.validation.mode=none anymore.
You shouldn't use find with entities just to check if an email address exists in
userService.isEmailUnique(value)
Why don't you create a method:
int countByEmail(String email)

Spring Boot Java implementation of customAnnotation

I know it's a possible duplicate and I found several threads like How can I find all beans with the custom annotation #Foo? or Custom annotation is not working on spring Beans but that's not really what I do or want to do.
I want an easy validator for the length of attributes of a class. Dont tell me about #Length or #Size. They're not helpful here. I tried several solutions and none of them did work.
CustomAnnotation:
#Target({ ElementType.METHOD, ElementType.FIELD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = CheckLengthValidator.class)
#Qualifier // got this one from a solution
public #interface CheckLength {
Class<?> className();
Class<?>[] groups() default {};
String message() default "List is not valid";
Class<? extends Payload>[] payload() default {};
}
CustomAnnotationValidator (methods not implemented yet):
public class CheckLengthValidator implements ConstraintValidator<CheckLength, List<Transaction>> {
#Override
public void initialize(CheckLength a) {
//get values which are defined in the annotation
System.out.println("init");
test = a.message();
}
#Override
public boolean isValid(List<Transaction> value, ConstraintValidatorContext context) {
for (Transaction x : value) {
if (x.getTimestamp().length() > 30) {
System.out.println("not valid");
return false;
}
}
return true;
}
}
So where do I use it? At my API where all autowired repos are.
#CrossOrigin(origins = "http://localhost:4200")
#RestController
public class FileManagementRestAPI {
#Autowired
#CheckLength(className = Transaction.class)
List<Transaction> transaction = new ArrayList<>();
...
}
Later this will be called to fill the list.
transaction.addAll((Collection<? extends Transaction>) csvToBean.parse());
What I tried:
I read about a solution which I later found out it is deprecated or not working anymore with CommandLineRunner and AnnotationConfigApplicationContext.
Then I've read that I have to declare it as Bean but a List here isn't a Bean or do I still need to do something with Beans? Saw something like this but didn't know what to do with it then:
public class InitBeans implements BeanPostProcessor { ... }
The error I get now:
Field transaction in com.stuff.project.apis.FileManagementRestAPI required a bean of type 'java.util.List' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
- #com.stuff.project.utils.CheckLength(message=List is not valid, groups=[], payload=[], className=class com.stuff.project.entity.Transaction)
Action:
Consider defining a bean of type 'java.util.List' in your configuration.
There were several other errors when I was trying to get it running.

ConstraintValidation not getting applied to the list

I have List of Objects that I need to run some validation on
#KeyValid
#Valid
protected List<KeyValue> keyValues;
and I have a the following annotation created for it:
#Target({ElementType.TYPE, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = KeyValidator.class)
public #interface KeyValid{
String message() default "invalid_parameter_default_message";
String[] checks() default {};
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And this is my validator:
public class KeyValidator implements ConstraintValidator<KeyValid, KeyValue> {
#Override
public void initialize(KeyValid keyValid) {
}
#Override
public boolean isValid(KeyValue keyValue, ConstraintValidatorContext constraintValidatorContext) {
return true;
}
}
I had read somewhere that collections can be validated in bulk if the list or map or set is annotated by custom constraint then all of the elements of the collection call the validator but the above code throws the following error
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'com.util.Validators.KeyValid' validating type 'java.util.List<com.model.KeyValue>'. Check configuration for 'keyValue'
Your constraint would get the actual List passed not its elements. If you are using Java 8 and the latest version of Hibernate Validator, you can use type argument constraints. You just have to make sure to also add ElementType.TYPE_USE to #Target in your constraint. Type argument constraints are not yet official part of Bean Validation, but will be in the next version of it (BV 2.0).
You would have something like this:
protected List<#KeyValid KeyValue> keyValues;
Alternatively, could you not put the #KeyValid constraint as class level constraint on KeyValue?

Using NotNull Annotation in method argument

I just started using the #NotNull annotation with Java 8 and getting some unexpected results.
I have a method like this:
public List<Found> findStuff(#NotNull List<Searching> searchingList) {
... code here ...
}
I wrote a JUnit test passing in the null value for the argument searchingList. I was expecting some type of error to happen but it went through as though the annotation was not there. Is this expected behavior? From what I understood, this was to allow you to skip writing the boilerplate null check code.
An explanation of what exactly #NotNull is supposed to do would be greatly appreciated.
#Nullable and #NotNull do nothing on their own. They are supposed to act as Documentation tools.
The #Nullable Annotation reminds you about the necessity to introduce an NPE check when:
Calling methods that can return null.
Dereferencing variables (fields, local variables, parameters) that can be null.
The #NotNull Annotation is, actually, an explicit contract declaring the following:
A method should not return null.
A variable (like fields, local variables, and parameters) cannot should not hold null value.
For example, instead of writing:
/**
* #param aX should not be null
*/
public void setX(final Object aX ) {
// some code
}
You can use:
public void setX(#NotNull final Object aX ) {
// some code
}
Additionally, #NotNull is often checked by ConstraintValidators (eg. in spring and hibernate).
The #NotNull annotation doesn't do any validation on its own because the annotation definition does not provide any ConstraintValidator type reference.
For more info see:
Bean validation
NotNull.java
Constraint.java
ConstraintValidator.java
As mentioned above #NotNull does nothing on its own. A good way of using #NotNull would be using it with Objects.requireNonNull
public class Foo {
private final Bar bar;
public Foo(#NotNull Bar bar) {
this.bar = Objects.requireNonNull(bar, "bar must not be null");
}
}
To make #NonNull active you need Lombok:
https://projectlombok.org/features/NonNull
import lombok.NonNull;
Follow: Which #NotNull Java annotation should I use?
If you are using Spring, you can force validation by annotating the class with #Validated:
import org.springframework.validation.annotation.Validated;
More info available here:
Javax #NotNull annotation usage
You could also use #NonNull from projectlombok (lombok.NonNull)
SO #NotNull just is a tag...If you want to validate it, then you must use something like hibernate validator jsr 303
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
Set<ConstraintViolation<List<Searching>> violations = validator.validate(searchingList);
I do this to create my own validation annotation and validator:
ValidCardType.java(annotation to put on methods/fields)
#Constraint(validatedBy = {CardTypeValidator.class})
#Documented
#Target( { ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidCardType {
String message() default "Incorrect card type, should be among: \"MasterCard\" | \"Visa\"";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And, the validator to trigger the check:
CardTypeValidator.java:
public class CardTypeValidator implements ConstraintValidator<ValidCardType, String> {
private static final String[] ALL_CARD_TYPES = {"MasterCard", "Visa"};
#Override
public void initialize(ValidCardType status) {
}
public boolean isValid(String value, ConstraintValidatorContext context) {
return (Arrays.asList(ALL_CARD_TYPES).contains(value));
}
}
You can do something very similar to check #NotNull.
To test your method validation in a test, you have to wrap it a proxy in the #Before method.
#Before
public void setUp() {
this.classAutowiredWithFindStuffMethod = MethodValidationProxyFactory.createProxy(this.classAutowiredWithFindStuffMethod);
}
With MethodValidationProxyFactory as :
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
public class MethodValidationProxyFactory {
private static final StaticApplicationContext ctx = new StaticApplicationContext();
static {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.afterPropertiesSet(); // init advisor
ctx.getBeanFactory()
.addBeanPostProcessor(processor);
}
#SuppressWarnings("unchecked")
public static <T> T createProxy(T instance) {
return (T) ctx.getAutowireCapableBeanFactory()
.applyBeanPostProcessorsAfterInitialization(instance, instance.getClass()
.getName());
}
}
And then, add your test :
#Test
public void findingNullStuff() {
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> this.classAutowiredWithFindStuffMethod.findStuff(null));
}
I resolved it with
#JsonSetter(nulls = Nulls.AS_EMPTY)
#NotBlank
public String myString;
Request Json:
{
myString=null
}
Response:
error must not be blank

How to set default group in bean validation context

I'm working with bean validations and I'm searching for a possibility to set a default group of my own bean validation annotation.
I have something (working) like this:
Application.class (calling validate on MyBean)
public class Application {
public static void main(String[] args) {
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<MyBean>> violations =
validator.validate(new MyBean(), SecondStep.class);
}
}
MyBean.class (the bean itself; here is what I want to prevent)
public class MyBean {
// I don't want to write this "groups" attribute every time, because it's very clear,
// that this should only be validated for the second step, isn't it?
#RequiredBySecondStep(groups=SecondStep.class)
private Object myField;
}
RequiredBySecondStep.class (the bean validation annotation)
#Documented
#Target(FIELD)
#Retention(RUNTIME)
#Constraint(validatedBy = RequiredBySecondStepValidator.class)
public #interface RequiredBySecondStep {
String message() default "may not be null on the second step";
Class<?>[] groups() default {}; // <-- here I want to set SecondStep.class
Class<? extends Payload>[] payload() default {};
}
RequiredBySecondStepValidator.class (an implemented constraint validator)
public class RequiredBySecondStepValidator implements ConstraintValidator<RequiredBySecondStep, Object> {
public void initialize(RequiredBySecondStep constraintAnnotation) {
}
public boolean isValid(Object object, ConstraintValidatorContext constraintContext) {
return object != null;
}
}
SecondStep.class (the bean validation group)
public interface SecondStep {
}
Unfortunately, it's not possible by specification, to set the default group in the RequiredBySecondStep annotation like this:
Class<?>[] groups() default SecondStep.class;
// and using just the following in the bean:
#RequiredBySecondStep
private Object myField;
This will result in a RuntimeException:
javax.validation.ConstraintDefinitionException: Default value for
groups() must be an empty array
Furthermore, there is not only a SecondStep. There are probably 5 different groups which I want to annotate directly with a #RequiredByFirstStep or #RequiredByFifthStep.
Is there a good way to implement this?
I think you got it all a bit wrong. There is indeed to way to do what you want and that's because the aspect of constraints and their validation via a ConstraintValidator is orthogonal to groups and groups sequences. Per design a constraint (annotation and its validator) should be independent of the group getting validated. Even if you would get this to work, it would not be portable constraints. Personally, I would re-think what you want to achieve. #RequiredByFirstStep does not tell you what the requirement is. You should develop constraints which imply what they are valiating (a string length, not null, etc), when or better in which condition they are executed is a then controlled by group interfaces.

Categories