Spring Boot & Hibernate Validation's ConstraintMappingContributor - spring-boot

The hibernate validations documentation describes how to create ConstraintMappingContributors here.
It states:
You then need to specify the fully-qualified class name of the
contributor implementation in META-INF/validation.xml, using the
property key hibernate.validator.constraint_mapping_contributors. You
can specify several contributors by separating them with a comma.
Given I have many of these, what would be the most appropriate way to auto-discover these i.e. via #Component and add them dynamically at runtime to the ConstrainMappingConfiguration during Spring Boot startup.
For example.. if a developer creates a new ConstraintMappingContributor, it should be picked up and added automatically when spring boot starts, requiring no other file changes.

This is what I came up with, seems to be working for me.
package...
import org.hibernate.validator.spi.cfg.ConstraintMappingContributor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
#Configuration
public class ValidationConfiguration {
private final List<ConstraintMappingContributor> contributors;
public ValidationConfiguration(Optional<List<ConstraintMappingContributor>> contributors) {
this.contributors = contributors.orElseGet(ArrayList::new);
}
#Bean
public LocalValidatorFactoryBean validatorFactory() {
return new ValidatorFactoryBean(this.contributors);
}
}
package...
import org.hibernate.validator.HibernateValidatorConfiguration;
import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
import org.hibernate.validator.spi.cfg.ConstraintMappingContributor;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import javax.validation.Configuration;
import java.util.List;
public class ValidatorFactoryBean extends LocalValidatorFactoryBean {
private final List<ConstraintMappingContributor> contributors;
ValidatorFactoryBean(List<ConstraintMappingContributor> contributors) {
this.contributors = contributors;
}
#Override
protected void postProcessConfiguration(Configuration<?> cfg) {
if (cfg instanceof HibernateValidatorConfiguration) {
HibernateValidatorConfiguration configuration = (HibernateValidatorConfiguration) cfg;
this.contributors.forEach(contributor -> contributor.createConstraintMappings(() -> {
DefaultConstraintMapping mapping = new DefaultConstraintMapping();
configuration.addMapping(mapping);
return mapping;
}));
}
}
}
I invoke it like this...
if(SpringValidatorAdapter.class.isInstance(this.validatorFactory)){
SpringValidatorAdapter.class.cast(this.validatorFactory).validate(entity, errors);
}

Related

Spring: ApplicationStartingEvent cannot be cast to ApplicationPreparedEvent for OptaPlanner Examination App

I'm a newbie and I try to start a Spring application linked to Optaplanner which will solve and place exams on a timetable.
I fixed the various issues with missing jars and I started the app on main.
However, it gives error:
Exception in thread "main" java.lang.ClassCastException: class org.springframework.boot.context.event.ApplicationStartingEvent cannot be cast to class org.springframework.boot.context.event.ApplicationPreparedEvent (org.springframework.boot.context.event.ApplicationStartingEvent and org.springframework.boot.context.event.ApplicationPreparedEvent are in unnamed module of loader 'app')
Here is the TimeTable class :
package models;
import java.util.List;
import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
#PlanningSolution
public class Timetable {
#ValueRangeProvider(id = "PeriodeRange")
#ProblemFactCollectionProperty
public List<Periode> periodeList;
#ValueRangeProvider(id = "SalleRange")
#ProblemFactCollectionProperty
public List<Salle> salleList;
#PlanningEntityCollectionProperty
public List<Examen> examenList;
#PlanningScore
public HardSoftScore score;
public void TimeTable(List<Periode> periodeList, List<Salle> roomList,
List<Examen> examenList) {
this.periodeList = periodeList;
this.salleList = roomList;
this.examenList = examenList;
}
public List<Periode> getperiodeList() {
return periodeList;
}
public List<Salle> getsalleList() {
return salleList;
}
public List<Examen> getexamenList() {
return examenList;
}
public HardSoftScore getScore() {
return score;
}
}
And the class that defines the solver :
package models;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import modeles.Timetable;
import org.optaplanner.core.api.solver.SolverJob;
import org.optaplanner.core.api.solver.SolverManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/Timetable")
public class TimetableController {
#Autowired
public SolverManager<Timetable, UUID> solverManager;
#PostMapping("/solve")
public Timetable solve(#RequestBody Timetable problem) {
UUID problemId = UUID.randomUUID();
// Submit the problem to start solving
SolverJob<Timetable, UUID> solverJob = solverManager.solve(problemId, problem);
Timetable solution;
try {
// Wait until the solving ends
solution = solverJob.getFinalBestSolution();
} catch (InterruptedException | ExecutionException e) {
throw new IllegalStateException("Solving failed.", e);
}
return solution;
}
}
And here is the main :
package models;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class TimeTableSpringBootApp {
public static void main(String[] args) {
System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(TimeTableSpringBootApp.class, args);
}
}
Any answer would be gladly appreciated.
Thank you in advance.
I suspect your project's dependency tree somehow has that spring class ApplicationStartingEvent twice in it's classpath (coming from different jars), which causes the class cast exception.
Try running mvn dependency:tree on your project and the optaplanner spring boot school timetabling quickstart. I suspect you're mixing spring versions in your dependency tree.

Is it possible to set the default date format in JSON-B (Yasson) globally, instead of adding an annotation on every property?

I have using Jersey so far and I am doing my first implementation with JSON-B.
I am using Payara, so I working with Jersey and Yasson. I had an issue, because the serialized dates would always contain the "[UTC]" suffix.
I have managed to use an annotation on my date property, in my DTO. But I would like to configure that globally (in the JAX-RS application config?), instead of repeating myself on every date property. Is that possible? I haven't found anything so far...
Side question: I assume that it is possible to get rid of this "[UTC]" suffix, since it breaks all clients trying to parse the date. Any idea?
Thanks to this Github issue, I was able to solve my problem. Here is what I ended up writing in my code:
JSONConfigurator.java:
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.json.bind.config.PropertyNamingStrategy;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
#Provider
public class JSONConfigurator implements ContextResolver<Jsonb> {
#Override
public Jsonb getContext(Class<?> type) {
JsonbConfig config = getJsonbConfig();
return JsonbBuilder
.newBuilder()
.withConfig(config)
.build();
}
private JsonbConfig getJsonbConfig() {
return new JsonbConfig()
.withDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", null);
}
}
And:
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;
#ApplicationPath("/api")
public class ApplicationConfig extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new HashSet<Class<?>>();
addRestResourceClasses(resources);
resources.add(JSONConfigurator.class);
return resources;
}
private void addRestResourceClasses(Set<Class<?>> resources) {
...
}
}

Spring find 2 candidates, but there is only one

I'm trying upgrade a JHipster project, however I found the following issue:
Description:
Parameter 0 of constructor in com.cervaki.config.AsyncConfiguration required a single bean, but 2 were found:
- jhipster-io.github.jhipster.config.JHipsterProperties: defined in null
- io.github.jhipster.config.JHipsterProperties: defined in null
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
What I understood is that spring can't inject the correct bean because there are two candidates, but I only have the io.github.jhipster.config.JHipsterProperties implementation:
package com.cervaki.config;
import io.github.jhipster.async.ExceptionHandlingAsyncTaskExecutor;
import io.github.jhipster.config.JHipsterProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.*;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
#Configuration
#EnableAsync
#EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {
private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
private final JHipsterProperties jHipsterProperties;
public AsyncConfiguration(JHipsterProperties jHipsterProperties) {
this.jHipsterProperties = jHipsterProperties;
}
#Override
#Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
log.debug("Creating Async Task Executor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(jHipsterProperties.getAsync().getCorePoolSize());
executor.setMaxPoolSize(jHipsterProperties.getAsync().getMaxPoolSize());
executor.setQueueCapacity(jHipsterProperties.getAsync().getQueueCapacity());
executor.setThreadNamePrefix("cervaki-Executor-");
return new ExceptionHandlingAsyncTaskExecutor(executor);
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
You can download the pom.xml here.
I did a search in the entire code and libs to find the jhipster-io.github.jhipster.config.JHipsterProperties file, however I didn't find anything.
What can I do to solve this problem?
I also faced this issue after generating new JhipsterApp,
And as you - I don't find the "jhipster-io" dependencies in project
How I solve this:
in src/main/java/your/package/config create a "AppConfiguration.java"
with content:
import io.github.jhipster.config.JHipsterProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
#Configuration
public class AppConfiguration {
#Bean
#Primary
public JHipsterProperties jHipsterProperties() {
return new JHipsterProperties();
}
}
even without #Primary - I haven't got this error

spring boot #Value always null

I am using spring boot 1.5.3 and trying to inject the properties from an application-dev.properties file into a Service bean but the value is always coming as null. The value does get loaded in my DevConfiguration class though.
I have a application class as below in my base package
package com.me;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
I have a configuration class as follows in
package com.me.print.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
#Configuration
#Profile("dev")
#PropertySources({
#PropertySource("classpath:application.properties"),
#PropertySource("classpath:application-dev.properties")
})
#ComponentScan(value = {"com.me.print.client"})
public class DevConfiguration {
#Value("${app.service.url}")
private String rootUri;
#Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
My Service bean that I am trying to load the value into is below
package com.me.print.client;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.me.print.model.zitResponse;
#Service
public class zitPrintClient {
private final RestTemplate restTemplate;
#Value("${app.service.url}")
private String rootUri;
public zitPrintClient(RestTemplateBuilder restTemplateBuilder) {
restTemplate = restTemplateBuilder
//.rootUri(rootUri)
.build();
}
public zitResponse getpooltatus(String poolId) {
return restTemplate.getForObject("/pool/{poolId}/#status",
zitResponse.class, poolId);
}
}
In the above class the rootURI is always null. Does anyone have any suggestions as to what I am missing
in my application-dev.properties file I have the following
app.service.url=http://localhost:8080/zitprint/v1
Thanks
UPDATE:
does anyone have any suggestions here as I tried to inject properties into my controller as follows:
#Value("${allowedVendors}") String allowedVendors
and if i put the above into a constructor it finds the value but does not find it otherwise:
public PController(#Value("${allowedVendors}") String allowedVendors) {
}
I cant use the property further in the code as with the constructor I have created two instances of the bean 1 via the constructor and the other created by spring DI. Any ideas why the value doesnt inject without the constructor
Thanks
You need to put it as a parameter in the constructor:
public zitPrintClient(RestTemplateBuilder restTemplateBuilder,
#Value("${app.service.url}") rootUri) {
this.rootUri = rootUri; // if you are only using this once,
// no need to keep this member variable around
restTemplate = restTemplateBuilder
.rootUri(rootUri)
.build();
}
The constructor gets called first when you are creating the object. The member variable, rootUri, would have it's value injected after the object is created. So, rootUri member variable would be null at the time the constructor is called.
(And, imho, for better readability, your class should start with a capital letter, i.e. ZitPrintClient, but it's your code ...)

Spring aop doesn't run when project starts

I'v implemented a spring-boot aop demo and it runs well, but when I want to use it to load some resource when the project starts, it doesn't work somehow
Aop:
package com.neo.mysql;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* Created by li_weia on 2017/7/6.
*/
#Aspect
#Component
public class DynamicDataSourceAspect {
#Before("#annotation(VendorSource)")
public void beforeSwitchDS(JoinPoint point){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
// 判断是否存在#DS注解
if (method.isAnnotationPresent(VendorSource.class)) {
VendorSource annotation = method.getAnnotation(VendorSource.class);
// 取出注解中的数据源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DataSourceContextHolder.setDB(dataSource);
}
#After("#annotation(VendorSource)")
public void afterSwitchDS(JoinPoint point){
DataSourceContextHolder.clearDB();
}
}
The VendorSource annotation:
package com.neo.mysql;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by li_weia on 2017/7/6.
*/
#Target({ ElementType.METHOD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface VendorSource {
String value() default "vendor-master";
}
It runs well here, I can successfully change datasource by annotation:
package com.neo.web;
import com.neo.entity.SiteEntity;
import com.neo.mapper.ClassMappingDao;
import com.neo.mysql.VendorSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
#RestController
public class UserController {
private final ClassMappingDao siteMapper;
#Autowired(required = false)
public UserController(ClassMappingDao siteMapper) {
this.siteMapper = siteMapper;
}
#RequestMapping("/getSites")
#VendorSource("vendor-read")
public List<SiteEntity> getUsers() {
return siteMapper.getAllSite();
}
}
but it doesn't work here, the aop method is not invoked at all:
package com.neo.component;
import com.neo.entity.SiteEntity;
import com.neo.mapper.ClassMappingDao;
import com.neo.mysql.VendorSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* Created by li_weia on 2017/7/7.
*/
#Component
public class TestComponent{
private final ClassMappingDao userMapper;
#Autowired(required = false)
public TestComponent(ClassMappingDao userMapper) {
this.userMapper = userMapper;
init();
}
#VendorSource("vendor-read")
public void init() {
List<SiteEntity> sites = userMapper.getAllSite();
for(SiteEntity site: sites){
System.out.println(site.getSite());
}
}
}
You need to fully qualify the annotation, like so:
#Before("execution(public * *(..)) && #annotation(com.neo.mysql.VendorSource)")
private void whatever() {}
Also, as mentioned in my comment above, you need to have spring-boot-starter-aop on classpath. Maybe you already do, but since you didn't say, it's worth mentioning.
Edit:
I didn't notice the real problem before, I wasn't paying attention.
Spring AOP only triggers if you make calls from another class. This is because Spring needs to be able to intercept the call and run the pointcut. Calling the method from constructor is not going to do anything.
You can do a hackish workaround. Create a #PostConstruct void postConstruct() {} method in your class (not constructor), autowire ApplicationContext, and then do MyClassWithInitMethod myClass = context.getBean(MyClassWithInitMethod.class) in the postConstruct method. Then call the method on myClass, and AOP will kick in.
Frankly, I didn't previously check what you are doing in your pointcut, and it's a terrible idea. When multiple threads run, they are going to overwrite the static context, and create a race-condition that you'll then create another question for. Don't do it! Use the factory pattern instead, and inject the DataSourceFactory in the classes that now have the annotation.

Resources