Spring Autowiring in Validator for Dropwizard - java

I'm using the Validator API to validate my Dropwizard resources.
At this point I need to create a custom ConstraintValidator -- but my validator must be able to connect to a database.
In order to handle this, I am using #Autowire inside my custom ConstraintValidator to provide my configured beans (the database).
public class CustomValidator implements ConstraintValidator<CustomAnnotation, String> {
#Autowired private ApplicationContext applicationContext;
#Override
public void initialize(final CustomAnnotation customAnnotation) {
}
#Override
public boolean isValid(final Optional<String> value, final ConstraintValidatorContext context) {
applicationContext.getApplicationName();
return true;
}
}
In order to get autowiring to work with the validator, I believe I have to use 'LocalValidatorFactoryBean'.
I've configured mine as such:-
final LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setApplicationContext(applicationContext); // preconfigured with my beans
factory.afterPropertiesSet(); // seems to perform the setup of the ConstraintValidatorFactory with applicationContext.getAutowireCapableBeanFactory()
Dropwizard comes with a validator pre-configured in the Environment object.
So, to ensure that Dropwizard uses the Spring configured validator with injection, I've set the environment with the LocalValidatorFactoryBean like so:-
environment.setValidator(factory);
However, rather frustratingly my #Autowired private ApplicationContext applicationContext from my CustomValidator still appears to be null.
Does anyone know where I'm going wrong here or if there is an easier way?
Edit A bit more info on how it was done.
Set this up in my application:-
final ValidatorFactory constraintValidatorFactory = // custom validator factory
final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
jerseyEnvironment.register(new ValidationConfigurationContextResolver(constraintValidatorFactory, factory));
Then used this class to replace the desired validator:-
public class ValidationConfigurationContextResolver implements ContextResolver<ValidationConfig> {
private final ConstraintValidatorFactory constraintValidatorFactory;
private final ValidatorFactory factory;
public ValidationConfigurationContextResolver(final ConstraintValidatorFactory constraintValidatorFactory,final ValidatorFactory factory) {
this.constraintValidatorFactory = constraintValidatorFactory;
this.factory = factory;
}
#Override
public ValidationConfig getContext(final Class<?> type) {
final ValidationConfig config = new ValidationConfig();
config.messageInterpolator(factory.getMessageInterpolator());
config.constraintValidatorFactory(constraintValidatorFactory); // custom constraint validator factory
config.parameterNameProvider(factory.getParameterNameProvider());
config.traversableResolver(factory.getTraversableResolver());
return config;
}
}

Related

Injected services become null in Custom Constraint Validators

I have tried to create a custom annotation with a constraint validator in the spring boot framework and I'm getting an error of injected services being null when executing the custom validator.
And, I have checked with the previous answers related to this. But, any answer didn't work for me now.
Some of them are as follows,
javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator
Autowired gives Null value in Custom Constraint validator
http://dolszewski.com/spring/custom-validation-annotation-in-spring/
My custom annotation and constraint validator as follows,
Custom Annotation
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Constraint(validatedBy = UniqueFoodNameValidator.class)
public #interface UniqueFoodName {
String message() default "{UniqueFoodName}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Custom Constraint Validator
public class UniqueFoodNameValidator implements ConstraintValidator<UniqueFoodName, String> {
private FoodService foodService;
#Autowired
public UniqueFoodNameValidator(FoodService foodService) {
this.foodService = foodService;
}
#Override
public void initialize(UniqueFoodName constraintAnnotation) {
}
#Override
public boolean isValid(String foodName, ConstraintValidatorContext context) {
return Objects.isNull(foodName) || foodService.get(foodName).isEmpty();
}
}
So, I would really appreciate If someone cloud help me to figure out the issue and to resolve this.
Note that: FoodService is an interface and it's methods implemented in the FoodServiceImpl class.
A bit late answer, but hope it will be useful for someone.
I suppose the problem is in your Hibernate configuration. The thing is that you validate the field twice. It happens because both Spring and Hibernate run validation automatically.
If you debug isValid method, you will see the flow runs into the method twice first time your FoodService will be injected, but not the second time.
To fix it you can disable Hibernate auto validation. For example:
private Properties hibernateSettings() {
Properties settings = new Properties();
// some settings
settings.put("javax.persistence.validation.mode", "none");
return settings;
}
#Bean
public LocalSessionFactoryBean getSessionFactory() {
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource());
sessionFactoryBean.setHibernateProperties(hibernateSettings());
sessionFactoryBean.setPackagesToScan("com.springmvc.models");
return sessionFactoryBean;
}

Use #ConfigurationProperties over non-managed #Bean

I would like to benefit from #ConfigurationProperties fantastic facilities without needing to expose the bean into my context. It is not a problem of #Primaries and the like, I simply cannot expose another Datasource into the context. How can I achieve the following?
#ConfigurationProperties("com.non.exposed.datasource.hikari")
public DataSource privateHikariDatasource() {
if (Objects.isNull(this.nonExposedDatasource)) {
this.nonExposedDatasource = this.nonExposedDatasourceProperties.initializeDataSourceBuilder().build();
}
return this.nonExposedDatasource;
}
Thanks to the answer by #LppEdd, the final perfect solution is:
#Autowired
private Environment environment;
public DataSource privateHikariDatasource() {
if (Objects.isNull(this.nonExposedDatasource)) {
this.nonExposedDatasource = bindHikariProperties(this.nonExposedDatasourceProperties.initializeDataSourceBuilder().build());
}
return this.nonExposedDatasource;
}
//This does exactly the same as #ConfigurationProperties("com.non.exposed.hikari") but without requiring the exposure of the Datasource in the ctx as #Bean
private <T extends DataSource> T bindHikariProperties(final T instance) {
return Binder.get(this.environment).bind("com.non.exposed.datasource.hikari", Bindable.ofInstance(instance)).get();
}
Then you can call your bean internally with this.privateHikariDatasource() to be used by your other beans.
Great thanks to #LppEdd!
Being that this DataSource is private to a class, and that containing class can be/is inside the Spring context, you can have a #ConfigurationProperties class
#ConfigurationProperties("com.foo.bar.datasource.hikari")
public class HikariConfiguration { ... }
Which, by registering it via #EnableConfigurationProperties, is available for autowiring
#EnableConfigurationProperties(HikariConfiguration.class)
#SpringBootApplication
public class Application { ... }
And thus can be autowired in the containing class
#Component
class MyClass {
private final HikariConfiguration hikariConfiguration;
private DataSource springDatasource;
MyClass(final HikariConfiguration hikariConfiguration) {
this.hikariConfiguration = hikariConfiguration;
}
...
private DataSource privateSingletonDataSource() {
if (Objects.isNull(this.springDatasource)) {
this.springDatasource = buildDataSource(this.hikariConfiguration);
}
return this.springDatasource;
}
}
buildDataSource will manually construct the DataSource instance.
Remember that you need to take care of synchronization when building the DataSource.
Final response is that you cannot re-use DataSourceProperties. You can't even extend it to change the properties' prefix. Only a single instance of it can exist inside the context.
The best you can do is mimic what Spring does.
Having
com.non.exposed.datasource.hikari.url=testUrl
com.non.exposed.datasource.hikari.username=testUsername
com.non.exposed.datasource.hikari.password=testPassword
...
You can define a new #ConfigurationProperties class
#ConfigurationProperties("com.non.exposed.datasource")
public class NonExposedProperties {
private final Map<String, String> hikari = new HashMap<>(8);
public Map<String, String> getHikari() {
return hikari;
}
}
Then, autowire this properties class in your #Configuration/#Component class.
Follow in-code comments.
#Configuration
public class CustomConfiguration {
private final NonExposedProperties nonExposedProperties;
private DataSource dataSource;
CustomConfiguration(final NonExposedProperties nonExposedProperties) {
this.nonExposedProperties= nonExposedProperties;
}
public DataSource dataSource() {
if (Objects.isNull(dataSource)) {
// Create a standalone instance of DataSourceProperties
final DataSourceProperties dataSourceProperties = new DataSourceProperties();
// Use the NonExposedProperties "hikari" Map as properties' source. It will be
// {
// url -> testUrl
// username -> testUsername
// password -> testPassword
// ... other properties
// }
final ConfigurationPropertySource source = new MapConfigurationPropertySource(nonExposedProperties.getHikari());
// Bind those properties to the DataSourceProperties instance
final BindResult<DataSourceProperties> binded =
new Binder(source).bind(
ConfigurationPropertyName.EMPTY,
Bindable.ofInstance(dataSourceProperties)
);
// Retrieve the binded instance (it's not a new one, it's the same as before)
dataSource = binded.get().initializeDataSourceBuilder().build();
}
// Return the constructed HikariDataSource
return dataSource;
}
}

CacheManager bean definition in Config.class leads to NoSuchBeanDefinitionException

I have a Spring service which is checking database entries. To minimize my repository calls both find methods are "#Cacheable". But when I try to init my service bean while my configuration class has a CacheManager bean definition I get following NoSuchBeanDefinitionException:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'foo.mediacode.directory.MediaCodeDirectoryService' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:353)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1093)
at foo.mediacode.directory.MediaCodeDirectoryService.implementation(MediaCodeDirectoryService.java:63)
at foo.campaigntree.directory.CampaignTreeDirectoryService.<init>(CampaignTreeDirectoryService.java:18)
... 15 more
If I take out the CacheManager bean definition, I can init my service bean and it runs without any problems and caching!
Here is my code:
Configuration
...
#Configuration
#EnableCaching
#EnableJpaRepositories(...)
#PropertySource({...})
public class MediaCodeDirectoryServiceConfig {
private static Logger configLogger = Logger.getLogger(MediaCodeDirectoryServiceConfig.class.getName());
#Value("${jpa.loggingLevel:FINE}")
private String loggingLevel;
#Value("${mysql.databaseDriver}")
private String dataBaseDriver;
#Value("${mysql.username}")
private String username;
#Value("${mysql.password}")
private String password;
#Value("${mysql.databaseUrl}")
private String databaseUrl;
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
...
}
#Bean
public MediaCodeDirectoryService mediaCodeDirectoryService() {
return new MediaCodeDirectoryService();
}
#Bean
public CacheManager mediaCodeCacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("mediaCodeMappingRegexCache"),
new ConcurrentMapCache("mediaCodeMappingsCache")));
return cacheManager;
}
#Bean
public JpaTransactionManager transactionManager() {
...
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
...
}
public DataSource getDataSource() {
...
}
public JpaDialect getJpaDialect() {
...
}
public Properties getEclipseLinkProperty() {
...
}
public JpaVendorAdapter getJpaVendorAdapter() {
...
}
}
Service
....
public class MediaCodeDirectoryService implements MediaCodeDirectoryServiceApi {
...
#Autowired
private MediaCodeDirectoryRepository repo;
#SuppressWarnings("resource")
public static MediaCodeDirectoryServiceApi implementation() {
if (INSTANCE == null) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MediaCodeDirectoryServiceConfig.class);
INSTANCE = ctx.getBean(MediaCodeDirectoryService.class);
}
return INSTANCE;
}
...
Repository
...
#Repository
public interface MediaCodeDirectoryRepository extends CrudRepository<MediaCodeDao, Integer> {
#Cacheable("mediaCodeMappingRegexes")
#Query("SELECT m FROM #{#entityName} m WHERE (m.fooId = :fooId) AND (m.isRegex = :isRegex) ORDER BY (m.orderId DESC, m.id ASC)")
List<MediaCodeDao> findByfooIdAndIsRegexOrderByOrderIdDescAndIdAsc(#Param("fooId") int fooId, #Param("isRegex") boolean isRegex);
#Cacheable("mediaCodeMappings")
List<MediaCodeDao> findByMediaCode(String MediaCode, Pageable pageable);
}
When I debug into DefaultListableBeanFactory I can find within beanDefinitionMap my mediaCodeDirectoryService and also within beanDefinitionNames mediaCodeDirectoryService appears. But DefaultListableBeanFactory.getBean(...) cannot resolve name and namedBean in line 364 is null.
When I try to get the context via String like:
INSTANCE = (MediaCodeDirectoryService) ctx.getBean("mediaCodeDirecotryService")
I avoid the NoSuchBeanDefinitionException but I run into an other one.
Anybody here has an idea on what might be the cause of this? Did I missed something in my configuration? Thx!
Caching is applied through AOP. For AOP Spring uses a proxy based approach and the default is to create interface based proxies.
public class MediaCodeDirectoryService implements MediaCodeDirectoryServiceApi {... }
With this class definition at runtime you will get a dynamically created class (Proxy$51 or something along those lines) which implements all interfaces but it isn't a MediaCodeDirectoryService. It is however a MediaCodeDirectoryServiceApi.
You have 2 ways of fixing this, either program to interfaces (which you should have been doing anyway because you have defined interfaces) instead of concrete classes or use class based proxies.
The first option involves you changing your code in the places the directly #Autowire or get an instance of MediaCodeDirectoryService to use MediaCodeDirectoryServiceApi instead (which imho you should already do, why else define an interface). Now you will get the proxy injected and everything will work.
The second option involves you setting proxyTargetClass=true on your #EnableCaching annotation. Then instead of an interface based proxy you will get a class based proxy.
#EnableCaching(proxyTargetClass=true)

Custom Hibernate Validator Spring Boot

I wish to use the Java 8 ReflectionParameterNameProvider Hibernate Validator to return proper parameter names instead of .argN e.g. getPerson.arg0
I am compiling the application with the -parameter flag and have added the following Bean to my context:
#Bean
public javax.validation.Validator validator() {
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.parameterNameProvider(new ReflectionParameterNameProvider())
.buildValidatorFactory();
return validatorFactory.getValidator();
}
But am still getting the old getPerson.arg0
Any ideas, Thanks?
If you are relying on Hibernate Validator's integration with Hibernate to perform the validation, I found that it was necessary to put all my configuration inside validation.xml and let Hibernate Validator bootstrap from that configuration instead.
The configuration you've specified will work for spring-specific things, but won't work for the Hibernate/Hibernate Validator integration unfortunately.
Figured it out, just had to set this in the methodValidationPostProcessor bean.
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
#Bean
public Validator validator() {
final ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.parameterNameProvider(new ReflectionParameterNameProvider())
.buildValidatorFactory();
return validatorFactory.getValidator();
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
final MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
Another solution, that also keeps all Spring defaults untouched, is to override method postProcessConfiguration():
#Bean
static LocalValidatorFactoryBean defaultValidator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean() {
#Override
protected void postProcessConfiguration(
javax.validation.Configuration<?> configuration) {
configuration.parameterNameProvider(new ReflectionParameterNameProvider());
}
};
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
return factoryBean;
}

Using I18n messages while validating forms with JSR-303

My app is fully-configured spring-boot app with thymeleaf templates engine. I18n configured as well so I can use it within my templates. Here is the config I use:
spring.messages.basename=i18n/messages
While manual validating fields I18n also work fine:
BindingResult result;
result.rejectValue("field", "some.i18n.code");
But once I want to implement some custom ConstraintValidator objects and use message field - no I18n involved, I receive plain codes as a response instead of a message. I.e.
{some.i18n.code}
I tried this solution - no result.
This on as well - same result.
What am I missing?
I guess I found the solution, maybe it will be helpful to others. All you have to do is to add the following definitions into your WebMvcConfigurerAdapter configuration implementation:
#Autowired
private MessageSource messageSource;
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
validatorFactoryBean.setValidationMessageSource(messageSource);
return validatorFactoryBean;
}
#Override
public Validator getValidator() {
return validator();
}
An alternative solution is just declare this bean in any of your #Configuration classes:
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean(MessageSource messageSource) {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource);
return bean;
}
Due to declaring this, now my custom error codes from my custom validators are being searched for in my messages.properties (that I also have in a i18n subdirectory by declaring spring.messages.basename=i18n/messages).
Example validator code:
public class ContestValidator implements ConstraintValidator<ValidContest, CreateContestParameters> {
#Override
public void initialize(ValidContest constraintAnnotation) {
}
#Override
public boolean isValid(CreateContestParameters contestParameters, ConstraintValidatorContext context) {
boolean result = true;
if (!endDateIsEqualOrAfterStartDate(contestParameters)) {
context.buildConstraintViolationWithTemplate("{Contest.endDate.invalid}")
.addPropertyNode("endDate").addConstraintViolation();
result = false;
}
if (!registrationDeadlineIsBeforeStartDate(contestParameters)) {
context.buildConstraintViolationWithTemplate("{Contest.registrationDeadline.invalid}")
.addPropertyNode("registrationDeadline").addConstraintViolation();
}
return result;
}
}

Categories