HV000028: Unexpected exception during isValid call - spring

I created a custom validation rule to check if a username exists in the database.
My User class has a username that has a custom validation rule that when an object is created it checks in the database if that same username exists.
I use an interface UserRepository extends JpaRepository<User, Integer> to save the users to the database and in the custom validation rule to check if the username already exists. I make use of
#Autowired
UserRepository userRepository;
Separately I can validate users and save them to the database, but when using them together like calling userRepository.save(user); the #Autowired UserRepository userRepository; in the custom validation rule does not get initiated so it remains null.
javax.validation.ValidationException: HV000028: Unexpected exception during isValid call.
Custom validation rule
public class UsernameConstraintValidator implements ConstraintValidator<Username, String> {
#Autowired
UserRepository userRepository;
#Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
return !userRepository.existsUserByUsername(s);
}
}
Trying to save user in the controller
#PostMapping("/save")
public String save(#Valid #ModelAttribute("user") User user,BindingResult result, #RequestParam String password2){
if(result.hasErrors()) return "register";
userRepository.save(user);
return "login";
}
Here is the full stack trace: https://justpaste.it/7rnx7
Here is the full project: https://github.com/leotrimvojvoda/todo

Add spring.jpa.properties.javax.persistence.validation.mode=none in application.propperties
I found the answer in this thread: Spring-Boot How to properly inject javax.validation.Validator
This answer includes the solution to the problem that I was having with bean validation and hibernate.
"...because Hibernate doesn't know about the Spring Context and as far as I can tell there is no way to tell it, not even with the LocalValidatorFactoryBean. This causes the Validator's to run twice. One correctly, and once that fails."
In short the solution is to tell hibernate to not run the validation by adding the following line in your application.propperties
spring.jpa.properties.javax.persistence.validation.mode=none
I suggest you read the full answer of this question to better understand it's implications with LocalValidatorFactoryBean.
Credits also go to João Dias for helping me find this answer.

Your Custom Validation class does not seem a Spring managed bean. Try adding #Component annotation to it as follows:
#Component
public class UsernameConstraintValidator implements ConstraintValidator<Username, String> {
#Autowired
UserRepository userRepository;
#Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
return !userRepository.existsUserByUsername(s);
}
}
Update 06/09/2021
As it seems (according to https://stackoverflow.com/a/13361762/16572295) you also need to setup a LocalValidatorFactoryBean to have this working:
#Bean
public Validator validatorFactory() {
return new LocalValidatorFactoryBean();
}

Related

Spring Boot with Validation - Inject repository inside custom validator gives Null

Although I found some similar problems (here and here for example) it did not completely answer my problem, as I used spring Boot 2.4.4, or simply don't work. My problem is that repository becomes null during second (JPA) validation. Here is the detail :
I use Spring Boot, and Bean Validation, via the spring-boot-starter-validation dependency.
I have a custom validator to check if an email exists in database. First I declare the #EmailExisting annotation :
#Documented
#Constraint(validatedBy = EmailValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface EmailExisting {
String message() default "Email already exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then the custom validator, with the repository injected to check Database :
public class EmailValidator implements
ConstraintValidator<EmailExisting, String> {
#Autowired
UserRepository userRepository;
#Override
public boolean isValid(String email,
ConstraintValidatorContext cxt) {
List<User> users = userRepository.findByEmail(email);
if (!users.isEmpty()) {
return false;
}
return true;
}
}
Finally the SPRING MVC part, with the #Valid annotation to trigger validation :
#Autowired
UserRepository userRepository;
#PostMapping(value = "/users")
public ResponseEntity addUSer(#Valid #RequestBody User user) {
user.setEmail(user.getEmail());
user.setLastName(StringUtils.capitalize(user.getLastName()));
user.setFirstName(StringUtils.capitalize(user.getFirstName()));
userRepository.save(user);
return new ResponseEntity(user, HttpStatus.CREATED);
}
Problem:
When I test the endpoint, I have a NullPointerException.
At first, during MVC validation the repository is correctly injected. But after, when we call save() method to save the entity in the controller, the control triggers again, and the repository becomes null, hence the NullPointerException.
I found a workaround, to deactivate control on JPA side, with the following configuration :
spring.jpa.properties.javax.persistence.validation.mode=none
It works well, but I think it should be possible to have the repository injected in the 2 cases?
All code is in my repository
Thank you in advance!

Spring Boot - Hibernate custom constraint doesn't inject Service

I will try to ignore other details and make it short:
#Entity
public class User
#UniqueEmail
#Column(unique = true)
private String email;
}
#Component
public class UniqueEmailValidatior implements ConstraintValidator<UniqueEmail,String>, InitializingBean {
#Autowired private UserService userService;
#Override
public void initialize(UniqueEmail constraintAnnotation) {
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(userService == null) throw new IllegalStateException();
if(value == null) return false;
return !userService.isEmailExisted(value);
}
}
This will work when the validation is made in Spring (Spring MVC #Valid or inject the Validator using #Autowire), everything will be fine.
But as soon as I save the entity using Spring Data JPA:
User save = userRepository.save(newUser);
Hibernate will try to instantiate a new UniqueEmailValidatior without inject the UserService bean.
So how can I make Hibernate to use my UniqueEmailValidatior component without it instantiate a new one.
I could disable hibernate validation using spring.jpa.properties.javax.persistence.validation.mode=none but I hope there is another way
Update: Here is my UserService:
#Autowired private Validator validator;
#Transactional
public SimpleUserDTO newUser(UserRegisterDTO user) {
validator.validate(user);
System.out.println("This passes");
User newUser = new User(user.getUsername(),
passwordEncoder.encode(user.getPassword()),user.getEmail(),
"USER",
user.getAvatar());
User save = userRepository.save(newUser);
System.out.println("This won't pass");
return ....
}
I would expect that Spring Boot would wire the existing validator to the EntityManager apparently it doesn't.
You can use a HibernatePropertiesCustomizer and add properties to the existing EntityManagerFactoryBuilder and register the Validator.
NOTE: I'm assuming here that you are using Spring Boot 2.0
#Component
public class ValidatorAddingCustomizer implements HibernatePropertiesCustomizer {
private final ObjectProvider<javax.validation.Validator> provider;
public ValidatorAddingCustomizer(ObjectProvider<javax.validation.Validator> provider) {
this.provider=provider;
}
public void customize(Map<String, Object> hibernateProperties) {
Validator validator = provider.getIfUnique();
if (validator != null) {
hibernateProperties.put("javax.persistence.validation.factory", validator);
}
}
}
Something like this should wire the existing validator with hibernate and with that it will make use of auto wiring.
NOTE: You don't need to use #Component on the validator the autowiring is build into the validator factory before returning the instance of the Validator.
To have the Spring beans injected into your ConstraintValidator, you need a specific ConstraintValidatorFactory which should be passed at the initialization of the ValidatorFactory.
Something along the lines of:
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.constraintValidatorFactory( new MySpringAwareConstraintValidatorFactory( mySpringContext ) )
.build();
with MySpringAwareConstraintValidatorFactory being a ConstraintValidatorFactory that injects the beans inside your ConstraintValidator.
I suspect the ValidatorFactory used by Spring Data does not inject the validators when creating them, which is unfortunate.
I suppose you should be able to override it. Or better, you should open an issue against Spring Boot/Spring Data so that they properly inject the ConstraintValidators as it the second time in a row we have this question on SO.
The answer is quite big to post here. Please check for this article in S.O to help you with. This should help you get started.
Test Custom Validator with Autowired spring Service
The problem is hibernate will no way know spring definition. However you can make Entities to be aware of any type of javax.validation types. Hope this helps.

In Spring, how do I invoke a public method from an autowired bean, in which the method is not part of the interface?

I'm using Spring 4.3.8.RELEASE. I have the following bean defined ...
import org.springframework.security.core.userdetails.UserDetailsService;
...
#Service
#Transactional(readOnly = true)
public class MyUserDetailsService implements UserDetailsService
{
...
public SpringboardAuthenticationUser getSpringboardAuthenticationUser(final User domainUser)
{
...
The method "getSpringboardAuthenticationUser" is a method I created, not part of the org.springframework.security.core.userdetails.UserDetailsService interface. How can I access this method from other places? For instance, in another part of my code I have
#Autowired
private UserDetailsService m_myUserDetailsSvc;
But clearly since the method is not part of the UserDetailsService interface, I cannot invoke it. So how can I invoke this public method?
Create a new Interface extending UserDetailsService and add your desired method.
public class MyUserDetailsService extends UserDetailsService {
...
public SpringboardAuthenticationUser getSpringboardAuthenticationUser(final User domainUser);
...
}
Then implement this interface.
public class MyUserDetailsServiceImpl implements MyUserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
}
#Override
public SpringboardAuthenticationUser getSpringboardAuthenticationUser(final User domainUser){
...
//Your logic here.
}
}
Autowire the new interface.
private MyUserDetailsService userDetailsService;
That's a good question. If the only implementation that you have of the UserDetailsService is the MyUserDetailsService, then, modify the interface so that it includes the method getSpringboardAuthenticationUser.
If you cannot modify the interface, then you can autowire directly the class that you want to use #Autowired MyUserDetailsService m_myUserDetailsSvc or even cast the UserDetailsService to MyUserDetailsService. If you want to do the cast, then I'd recommend to use the #Qualifier("MyUserDetailsService") to force Spring to inject a bean of this type.
Technically you can just cast UserDetailsService to MyUserDetailsService and invoke method directly:
if (m_myUserDetailsSvc instanceof MyUserDetailsService) {
final SpringboardAuthenticationUser user = MyUserDetailsService.class.cast(m_myUserDetailsSvc).getSpringboardAuthenticationUser();
}
But from design perspective I'll recommend to reconsider it. The whole point of doing dependency injection is to lower coupling between components and such actions/intentions forcing you to do the opposite thing.
Why don't you make getSpringboardAuthenticationUser() method a part of your UserDetailsService interface? In that case it will become a contract and can be sure that whatever implementation was injected to you class by DI container it will satisfied a contract and be able to retrieve that user for you.
Update per comment
In case your beans get wrapped by proxy(s) you can call isAssignableFrom instead of instanceOf.
if (m_myUserDetailsSvc.getClass().isAssignableFrom(MyUserDetailsService.class)) {
MyUserDetailsService.class.cast(m_myUserDetailsSvc).getSpringboardAuthenticationUser();
}

Spring alternative for Factory

May be its a duplicate, Please feel free to tag... I am a newbie to Spring.
I am implementing a UserService for getting user details from different vendors,
So My class Structure is
Interface UserService ->> UserServiceA, UserServiceB
Which user service to use depends upon a field called provider. my code will look something like
public interface ExternalUserService {
ExternalUserDTO getUserDetail(String username);
}
Implementations -
public class GoogleUserService implements ExternalUserService{
#Override
public ExternalUserDTO getUserDetail(String username) {
return user;
}
}
public class FacebookUserService implements ExternalUserService{
#Override
public ExternalUserDTO getUserDetail(String username) {
return user;
}
}
I want to use it in my code in this fashion, I dont know if this is possible, but giving a try to see if its possible
public class ExternalUserManager(String provider) {
String provider;
#Autowired
ExternalUserService service; //This is supposed to come from some factory, dont know how to get it in spring context.
public void doSomething(String username) {
System.out.println(service.getUserDetail(username));
}
}
Had it been in conventional java programming, I would have created a Factory called UserServiceFactory, which would have made the things straight.
Can someone please help me on how much it is possible with spring, and if its possible, then how can I achieve it? We use Spring boot, so no xml config.
You can use a #Bean annotated method with scope 'prototype' as a factory.
Spring will call this method anytime this bean is injected somewhere.
import org.springframework.beans.factory.config.BeanDefinition;
...
#Bean
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public ExternalUserService externalUserService(UserServiceFactory factory,UserProviderResolver resolver) {
.. create the user service depending on resolver.getProvider()
}
The UserServiceFactory is used to create the specific service depending on the provider name, as you already described.
Create a class UserProviderResolver whith a method getProvider() that returns the provider name for the current request or user.
You can #Autowire the HttpRequest in the UserProviderResolver to get access to the current request.

Don't insert duplicates with RepositoryRestResource

I have my repository class which exposes REST interface
#RepositoryRestResource(collectionResourceRel = "user", path = "user")
public interface UserRepository extends CrudRepository<User, Integer> {
}
I want to avoid inserting duplicate objects via POST requests.
I protected my database with constraints and now it's OK on DB side. But there are exceptions in log file on every attempt to insert duplicate objects.
I can implement a controller where I manage POST requests and do necessary checks by myself.
But for me it's a pretty common task which may already be implemented in Spring.
What is the canonical and simple way to avoid duplicates?
You can create and register a 'before create' Application Event listener as detailed in the manual:
http://docs.spring.io/spring-data/rest/docs/current/reference/html/#events
#RepositoryEventHandler
public class UserEventHandler {
#Autowired
private UserRepository repository;
#HandleBeforeCreate
public void handleUserCreate(User user) {
//check database
if(duplicate){
throw new DuplicateUserException(user);
}
}
}
You can register a #ControllerAdvice to return some meaningful response. For example:
#ControllerAdvice
public class ExceptionHandlingAdvice{
#ExceptionHandler(DuplicateUserException.class)
#ResponseStatus(HttpStatus.CONFLICT)
#ResponseBody
public Map<String, Object>
handleDuplicateUserException(DuplicateUserException exception){
Map<String, Object> map = new LinkedHashMap<>();
map.put("duplicateUser", exception.getUser().getUserName());
return map;
}
}

Resources