Unable to read application.yml in Condition class - spring

I have this class
#Service
#Conditional(MyCondition.class)
public class Class1 implements AcknowledgingMessageListener<String, GenericRecord> {
#Value("#{'${audit.module}'}")
private String xyz;
Here is MyCondition
#Configuration
#Component
#EnableConfigurationProperties
#ConfigurationProperties("audit")
public class MyCondition implements Condition {
#Value("#{'${audit.module}'}")
private String ss;
}
The problem is in MyCondition #Value is unable to read the value from from application.yml.

MyCondition is not a spring managed bean when you use it as a #Conditional. So you can not use #Value inside MyCondition. However there are some workaround.
You can set String ss as static and add one setter value with #Value.
#Component
public class MyCondition implements Condition {
private static String ss;
#Value("${audit.module}")
public void setSs(String ss) {
MyCondition.ss = ss;
}
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return true;
}
}
But this value will be set after Class1 has been created, so at the time on validating #Conditional ss will be null.
But I think you will need to use the value of ss inside the matches method. So inside the matches method you can access property values with ConditionContext
public class MyCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println(context.getEnvironment().getProperty("audit.module"));
return true;
}
}

Related

Use #Value when defining custom #Conditional

I would like to create custom condition that will be pass as value to #Conditional interface applied on #Configuration class. Basically I would like to create beans from Config1 or Config2 depending on a configuration that is stored in a database - dbConfig.get('configType') connects to the database and returns the value. However it looks that dbConfig is not created at that time yet. What could be a way to resolve it? I would like to avoid if-else within a bean definition to differentiate creation code.
#Configuration
#Conditional(OnCondition1.class)
public class Config1{
// beans definitions
}
#Configuration
#Conditional(OnCondition2.class)
public class Config2{
// beans definitions
}
public class OnCondition1 implements Condition{
#Value("#{dbConfig.get('configType')}")
private String configType;
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return configType.equals('1');
}
}
public class OnCondition2 implements Condition{
#Value("#{dbConfig.get('configType')}")
private String configType;
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return configType.equals('2');
}
}

How to validate a single value but not annotated pojo with specific ConstraintValidator in spring boot

My purpose is to use a specific ConstraintValidator in 2 scenarios below
use annotations on POJO to validate the whole object (the popular way)
validate a single value with specific validator's isValid function (for some configurable dynamic validation request)
The validator must support services injection, so I must get it from spring but not create a new validator instance manually.
followed my test codes:
annotation
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { IdNumberValidator.class })
public #interface IdNumber {
String message() default "id number is not available";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
validator
public class IdNumberValidator implements ConstraintValidator<IdNumber, String>, BeanNameAware {
#Autowired
private IUserService usrService;
private String bn;
#Override
public boolean isValid(String value, ConstraintValidatorContext context){
System.out.println("my name is:" + bn);
return usrService.getUserByAccount(value).isPresent();
}
#Override
public void setBeanName(String name) {
this.bn = name;
}
}
pojo
#Getter
#Setter
public class TestPOJO {
private long id;
#IdNumber
private String idn;
}
service
#Service
public class TestValidatorService {
#Autowired
private Validator validator;
#Autowired
private ApplicationContext context;
public void validatePojo(TestPOJO pojo){
BeanPropertyBindingResult e = new BeanPropertyBindingResult(pojo, "TestPOJO");
validator.validate(pojo, e);
if(e.hasErrors()){
for(ObjectError oe : e.getAllErrors()){
System.out.println(oe.toString());
}
}
}
public void validatePojoByDynamicValidator(TestPOJO pojo){
IdNumberValidator validator = context.getBean("com.test.IdNumberValidator", IdNumberValidator.class); // got the name via BeanNameAware but seems not working
System.out.println(validator.isValid(pojo.getIdn(), null));
}
}
In the test case for service, function "validatePojo" passed but "validatePojoByDynamicValidator" did not.
Any solution for this problem? Thanks!

How to use Conditional interface in spring boot with value inside a list

I have application-sample.YAML file where I have data. After loading the data, based on certain fields. I want to decide which few components to load or not to load. I can see this below condition class loads first, as a result, I am getting the data null because this condition class is loading first before my Data loaded.
#Configuration
public class AwsCondition implements Condition {
#Autowired
MyTestData data;
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// TODO Auto-generated method stub
log.info("inside...........condition");
if(data.getListeners().size()>1){
return true;
}
return false;
}
}
MyTestData.java
#Slf4j
#Data
#ConfigurationProperties
#Component
public class MyTestData implements InitializingBean {
List<Listener> listeners = new ArrayList<>();
#Data
public static class Listener {
private String type;
private String name;
}
#Override
public void afterPropertiesSet() throws Exception {
if (this.getListeners().isEmpty()) {
log.info("Nothing configured. Please verify application-test.yml is configured properly.");
System.exit(1);
}
}
}
ConditionTest class
#Component
#Conditional(AwsCondition.class)
public class TestS3ListenerRouter extends RouteBuilder {
//My all logic lying here
}
Try the following:
#Component
public class AwsCondition implements Condition {
#Autowired
MyTestData data;
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// TODO Auto-generated method stub
log.info("inside...........condition");
if(data.getListeners().size()>1){
return true;
}
return false;
}
}

Hibernate validation - autowired returns null

After looking around, I couldn't find any good solution to this.
My autowired didn't work as expected where it returns null. I've autowired this particular class in other classes and it works so it only doesn't work in constraintvalidator classes.
UserService class
#Service
public class UserService {
#Autowired
private UserRepository userRep;
public void addUser(User user) {
userRep.save(user);
}
public void deleteUser(long userId) {
userRep.deleteById(userId);
}
public List<User> retrieveAllUsers(){
Iterable<User>temp =userRep.findAll();
List<User>allUsers = null;
temp.forEach(allUsers::add);
return allUsers;
}
public boolean searchByEmail(String email) {
return userRep.findByEmail(email);
}
public void updateUser(User user) {
userRep.save(user);
}
}
Annotation interface class
#Target(ElementType.FIELD)
//When will the annotation be processed compilation, runtime etc
#Retention(RetentionPolicy.RUNTIME)
//Where is the logic
#Constraint(validatedBy = EmailValidator.class)
#Documented
public #interface ValidEmail {
//Error message
String message() default "Invalid email";
//Required for annotation
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Annotation logic class. The autowired here returns null
public class EmailValidator implements ConstraintValidator<ValidEmail, String> {
#Autowired
private UserService service;
//Actual place to place the logic to check if the data is valid or not
#Override
public boolean isValid(String email, ConstraintValidatorContext context) {
if (email == null) {
return false;
}
List<User> users = service.retrieveAllUsers();
if (users.size() > 0) {
return Pattern.matches("(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")#(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])", email)
&& service.searchByEmail(email);
}
else {
return Pattern.matches("(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")#(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])", email);
}
}
#Override
public void initialize(ValidEmail validEmail) {
validEmail.message();
}
}
Main
#SpringBootApplication
#ComponentScan(basePackages = {
"com.Alex.Mains", "com.Alex.UserPackage", "com.Alex.Flights", "com.Alex.Security"
})
#EntityScan( basePackages = {"com.Alex.UserPackage", "com.Alex.Flights"})
#EnableJpaRepositories({"com.Alex.UserPackage", "com.Alex.Flights"})
public class JpaApplication {
public static void main(String[] args) {
SpringApplication.run(JpaApplication.class, args);
}
// #Bean
// public Validator validator(final AutowireCapableBeanFactory beanFactory) {
//
// ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
// .configure()
// .constraintValidatorFactory(new SpringConstraintValidatorFactory(beanFactory))
// .buildValidatorFactory();
//
// return validatorFactory.getValidator();
// }
}
Edit: Tried #Componenet
Fixed with adding the following to application.properties. No idea why but it works
spring.jpa.properties.javax.persistence.validation.mode=none
EDIT: My Suggestion
Instead of a custom validator, use the existing #EMail and a unique constraint:
#Entity
public class User {
// ...your properties
#Email
#Column(unique = true)
private String email.
// Rest of class...
}
OLD:
So, first off:
List<User> users = service.retrieveAllUsers();
if (users.size() > 0) {
You are fetching all the Users from the database, just to check whether any users exists? This is very, very inefficient. If you are already using Spring Data, you can just do
#Query("SELECT COUNT(*) > 0 FROM Users")
boolean anyExists();
Furthermore, your Service does not get injected, because EmailValidator is a POJO (plain old java object) and not a Spring managed component. If you annotate it with #Component or #Service Spring will take care of injection.
But I would not recommend that. I'm not sure what your exact use case is, but validators are often used on Entities and as such, they get called when the entity is created or updated. You don't want to issue additional queries in those cases.
Like I said, I don't know what exactly you are trying to achieve, but you could use the existing #Email validator (you can even provide a custom regular expression with the regexp attribute).

Why are spring beans validated even if the condition says it should not be loaded into the context?

Given the example below, I would expect MyConfig.getSrvConfig() would not be called and therefore no validation would be executed on the returned object neither.
But for some reason the validation is executed and the test case fails. Is there anything wrong in this setup?
I know the test would pass if I have private MySrvConfigBean srvConfig not initialized at declaration - but I really don't want MySrvConfigBean to be a standalone class with a #ConfigurationProperties(prefix = "cfg.srvConfig") annotation.
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = { TestCaseConfiguration.class })
public class ConditionalConfigValidationTest {
#Autowired
private ApplicationContext applicationContext;
#Test
public void test() {
assertNotNull(applicationContext);
assertFalse("srvConfig must NOT be in context", applicationContext.containsBean("srvConfig"));
}
#Configuration
#EnableConfigurationProperties(value = { MyConfig.class })
public static class TestCaseConfiguration {
}
#Component
#Validated
#ConfigurationProperties(prefix = "cfg")
public static class MyConfig {
private MySrvConfigBean srvConfig = new MySrvConfigBean();
#Bean
#Valid
#Conditional(MyCondition.class)
public MySrvConfigBean getSrvConfig() {
return srvConfig;
}
public static class MySrvConfigBean {
#NotNull
private String name;
public String getName() {
return name;
}
}
}
public static class MyCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
}
The reason we would like to have it this way is, because we then are able to structure configuration in code the same way as we have it in the YAML file, e.g.: (cfg and cfgA are the "root" objects for two different configuration hierarchies).
cfg:
srvConfig:
name: Dude
clientConfig:
xxx: true
yyy: Muster
cfgA:
aaaConfig:
bbb: false
ccc: Dundy
dddConfig:
fff: 3
It feels like the execution of the validation (triggered by #Valid on getSrvConfig()) is a bug in this case.
Apparently this is not supported and should be solved in a different way:
#Configuration
#Conditional(MyCondition.class)
#EnableConfigurationProperties(value = { MyConfig.class })
public static class TestCaseConfiguration {
}

Resources