#Autowired Spring #Component with #ConditionalOnProperty - spring

Can I use #Autowired Spring 4.x #Components with a #ConditionalOnProperty to choose implementations of a Feature based on a featuretoggles.properties file?
public class Controller {
#Autowired
private Feature feature;
}
#Component
#ConditionalOnProperty(name = "b", havingValue = "off")
public class A implements Feature {
}
#Component
#ConditionalOnProperty(name = "b", havingValue = "on")
public class B implements Feature {
}
#Configuration
#PropertySource("classpath:featuretoggles.properties")
public class SomeRandomConfig {
}
With a src/main/resources/featuretoggles.properties file:
b = on
(That the name of the toggle "b" and the name of the class "B" match is coincidence; it's not my aim to have these equal, the toggle could have any name.)
This fails to auto-wire feature in the Controller with an UnsatisfiedDependencyException, saying "No qualifying bean of type 'Feature' available: expected at least 1 bean that qualifies as autowire candidate".
I know I can realize this with a #Configuration class that chooses a #Bean depending on the property. But when I do that I have to add a new Configuration class each time I add a feature toggle, and those Configuration classes will be highly similar:
#Configuration
#PropertySource("classpath:featuretoggles.properties")
public class FeatureConfig {
#Bean
#ConditionalOnProperty(name = "b", havingValue = "on")
public Feature useB() {
return new B();
}
#Bean
#ConditionalOnProperty(name = "b", havingValue = "off")
public Feature useA() {
return new A();
}
}

I did what you're trying to do by following this guide. First step was to write a Condition...
public class OnEnvironmentPropertyCondition implements Condition
{
#Override
public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata meta)
{
Environment env = ctx.getEnvironment();
Map<String, Object> attr = meta.getAnnotationAttributes(
ConditionalOnEnvProperty.class.getName());
boolean shouldPropExist = (Boolean)attr.get("exists");
String prop = (String)attr.get("value");
boolean doesPropExist = env.getProperty(prop) != null;
// doesPropExist shouldPropExist result
// true true true
// true false false
// false true false
// true false true
return doesPropExist == shouldPropExist;
}
}
...then an annotation using that condition.
/*
* Condition returns true if myprop exists:
* #ConditionalOnEnvProperty("myprop")
*
* Condition returns true if myprop does not exist
* #ConditionalOnEnvProperty(value="myprop", exists=false)
*/
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Conditional(OnEnvironmentPropertyCondition.class)
public #interface ConditionalOnEnvProperty
{
public String value();
public boolean exists() default true;
}
You can add featuretoggles.properties to the environment with the #PropertySource annotation.

Related

Spring converter can't be diseable conditional

i use a Converter for OffsetDateTime for store they in SQLServer , but I dont want use it on H2, so i have try many think but it never work is always active....
My converter :
#Conditional(OffsetDateTimeConverterCondition.class)
#Converter(autoApply = true)
public class OffsetDateTimeConverter implements AttributeConverter<OffsetDateTime, String> {
private static final DateTimeFormatter FORMATTER_FROM_DB
= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.nnnnnnn xxx");
private static final DateTimeFormatter FORMATTER_TO_DB
= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.nnnnnnnnn xxx");
#Override
public String convertToDatabaseColumn(OffsetDateTime attribute) {
return attribute == null ? null : attribute.format(FORMATTER_TO_DB);
}
#Override
public OffsetDateTime convertToEntityAttribute(String dbData) {
return dbData == null ? null : OffsetDateTime.parse(dbData, FORMATTER_FROM_DB);
}
}
My class of condition :
public class OffsetDateTimeConverterCondition extends AnyNestedCondition {
public OffsetDateTimeConverterCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
#ConditionalOnProperty(name = "required.datasource.dbms", havingValue = "SQLSERVER")
static class SQLSERVERCondition {
}
#ConditionalOnProperty(name = "required.datasource.dbms", havingValue = "ORACLE")
static class ORACLECondition {
}
}
I have try to :
#Configuration
public class OffsetDateTimeConverterCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return Arrays.asList(DBMS.SQLSERVER.name(), DBMS.ORACLE.name())
.contains(context.getEnvironment()
.getProperty("required.datasource.dbms"));
}
}
The setting is correctly set to SQLSERVER or H2, OffsetDateTimeConverterCondition return false when I am on H2 but it style active.

Sending #Value annotated fields to a DTO layer returns null

I have a class which is composed of 2 different objects :
public class MyClass{
private OptionClass optionClass;
private ConstantClass constantClass;
public DocumentToSignRestRequest(OptionClass optionClass, ConstantClass constantClass) {
this.optionClass= optionClass;
this.constantClass= constantClass;
}
}
My first class is a classic POJO. My second class retrieve values from the application.properties file.
public class ConstantClass {
#Value("${api.url}")
private String hostName;
#Value("${sign.path}")
private String pathStart;
public ConstantClass () {
this.hostName= getHostName();
this.path = getPath();
}
I map MyClass with MyClassDto in order to call a service.
#PostMapping(
value="/sign",
consumes = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE },
produces = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE }
)
public MyClassRest prepareDocument(#RequestBody DocumentToPrepare documentToPrepare) throws Exception {
MyClassRest returnValue = new MyClassRest ();
ModelMapper modelMapper = new ModelMapper();
MyClassDto myClassDto = modelMapper.map(documentToPrepare, MyClassDto .class);
DocumentDto signedDocument = documentService.signDocument(documentDto);
returnValue = modelMapper.map(signedDocument, DocumentRest.class);
return returnValue;
}
My DTO class work fine and retrieve the OptionClass datas, but concerning the second Class, i obtain null as value, while i try to print it out in the service layer.
Your ConstantClass should be a Bean or a Component (as #cassiomolin says in comments)
#Component
public class ConstantClass {
private String hostName;
private String pathStart;
public ConstantClass (#Value("${api.url}") String url, #Value("${sign.path}") String path ) {
this.hostName = url;
this.pathStart = path;
}
// getters...
Then you can easily inject this component in your Controller and use it.
#Controller
public class YourController(){
private ConstantClass constantClass;
public YourController(ConstantClass constantClass){
this.constantClass = constantClass;
}
#PostMapping("...")
public MyClass post(.....){
.....
MyClass myclass = new MyClass(this.constantClass,...)
.....
}
}
note that Spring can autowire #Value and #Component, ... via the constructor; that can be very useful when you do unit-testing

conditional #Autowired?

I have a HsqldbReconciler (for "work" with a HSQLDB database) which I autowired, like:
#Autowired
HsqldbReconciler hsqldbReconciler;
In Future there will be a OracleReconciler, MssqlReconciler, etc. I will need to use them accordingly to the type of connection a user has chosen.
How should I implement this? Usually I would have a kind of factory, which returns only the needed Reconciler. The only way in spring, I can currently imagine, is to Autowire an instance of each Reconciler, then use one of them in the code. Is there a better way?
make a Factory Class that will contain all your beans, e.g
#Component
class Factory{
#Autowired HsqldbReconciler hsqldb;
#Autowired OracleReconciler oracle;
#Autowired MssqlReconciler mssql;
public Object getInstance(String type){
switch(type){
case "mssql" : return mssql;
case "oracle" : return oracle;
// and so on
default : return null;
}
}
}
Now use this Factory as follows
class SomeClass{
#Autowired private Factory factory;
public Object someMethod(){
Object reconciler = factory.getInstance("mssql");
((MssqlReconciler)reconciler).someMethod();
}
}
Define them in your Config with the same name, but different conditions:
#Bean(name = "dbReconciler")
#Conditional(HsqldbReconcilerEnabled.class)
public ReconcilerBase getHsqldbReconciler() {
return new HsqldbReconciler();
}
#Bean(name = "dbReconciler")
#Conditional(OracleReconcilerEnabled.class)
public ReconcilerBase getOracleReconciler() {
return new OracleReconciler();
}
#Bean(name = "dbReconciler")
#Conditional(MssqlReconcilerEnabled.class)
public ReconcilerBase getMssqlReconciler() {
return new MssqlReconciler();
}
create conditions reading from app.properties:
HsqldbReconciler.enabled=true
OracleReconciler.enabled=false
MssqlReconciler.enabled=false
like this:
public class HsqldbReconcilerEnabled implements Condition {
private static final String PROP_ENABLED = "HsqldbReconciler.enabled";
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String property = context.getEnvironment().getProperty(PROP_ENABLED);
return Boolean.parseBoolean(property);
}
}
// etc...
use like:
#Autowired
#Qualifier("dbReconciler")
ReconcilerBase dbReconsiler;
ensure you're not enabling multiple beans at the same time.

Spring validator fails looking up value of class level constraint

I'm getting an error with JSR 303 class level validation and spring and I'd like to know if I am setting things up in the correct way.
I'm using validation in spring (4.2.6.RELEASE) using hibernate-validator (5.2.4.Final) with a typical setup inside a spring controller like:
#RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public SomeDto update(#PathVariable Integer id, #Valid #RequestBody SomeDto someDto) {
...
return someDto;
}
This works fine with most setups, but when the target of the validation includes a set of objects that are annotated with a Class level validation the SpringValidatorAdaptor fails when trying to lookup the original value
The following code illustrates the problem:
Target class to validate:
public class Base {
#Valid
Set<MyClass> myClassSet = new HashSet<>();
public Set<MyClass> getMyClassSet() {
return myClassSet;
}
Class with class level validation annotation:
#CheckMyClass
public class MyClass {
String a;
String b;
public MyClass(final String a, final String b) {
this.a = a;
this.b = b;
}
}
Constraint:
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = CheckMyClassValidator.class)
#Documented
public #interface CheckMyClass {
String message() default "Invalid class";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Validator:
public class CheckMyClassValidator implements ConstraintValidator<CheckMyClass, MyClass> {
#Override
public void initialize(final CheckMyClass constraintAnnotation) {
}
#Override
public boolean isValid(final MyClass value, final ConstraintValidatorContext context) {
return false; // want it to fail for testing purposes
}
}
Test class:
#SpringBootApplication
public class SpringValidationTest {
#Bean
public org.springframework.validation.Validator validator() {
return new org.springframework.validation.beanvalidation.LocalValidatorFactoryBean();
}
private void doStandardValidation() {
Base base = createBase();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Base>> violations = validator.validate(base);
for (ConstraintViolation<?> violation : violations) {
System.out.println(violation.getMessage());
}
}
private Base createBase() {
Base base = new Base();
base.getMyClassSet().add(new MyClass("a1", "b1"));
// base.getMyClassSet().add(new MyClass("a2", "b2"));
return base;
}
#PostConstruct
private void doSpringValidation() {
Base base = createBase();
org.springframework.validation.Validator validator = validator();
DataBinder binder = new DataBinder(base);
binder.setValidator(validator);
binder.validate();
BindingResult results = binder.getBindingResult();
for (ObjectError error : results.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
public static void main(String[] args) {
(new SpringValidationTest()).doStandardValidation();
System.out.println();
ApplicationContext applicationContext = SpringApplication.run(SpringValidationTest.class);
}
}
The standard validation works fine, but when it is wrapped by the spring validator (as in the typical controller setup) it ends up with an exception (as below) trying to lookup the value of the property:
Caused by: java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:1.8.0_45]
at java.lang.Integer.parseInt(Integer.java:592) ~[na:1.8.0_45]
at java.lang.Integer.parseInt(Integer.java:615) ~[na:1.8.0_45]
at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyValue(AbstractNestablePropertyAccessor.java:657) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
... 37 common frames omitted

How to make a bean configuration trigger only when two profiles are active

I have a bean that only should get created iff the two profiles "demo" and "local" both are active. What is the best way to achieve this in a java based Spring Configuration.
What I came up with so far is, creating beans like the following:
#Profile("demo")
#Bean("isDemoActive")
public Boolean isDemoActive(){ return true;}
And get those injected in the bean creating method and do a if condition on those beans.
Is there a nicer/easier way to do this kind of stuff?
Here's my suggestion, as per my comment above:
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class DoubleProfilesCondition implements Condition {
public boolean matches(ConditionContext context,AnnotatedTypeMetadata metadata) {
String[] activeProfiles = context.getEnvironment().getActiveProfiles();
int counter = 0;
for (int i = 0; i < activeProfiles.length; i++) {
String profile = activeProfiles[i];
if (profile.equals("profile1") || profile.equals("profile2")) {
counter++;
}
}
if (counter == 2)
return true;
return false;
}
}
And the class that dictates which beans are created:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
#Configuration
#Conditional(DoubleProfilesCondition.class)
public class MyConfig {
public #Bean
ExampleService service() {
ExampleService service = new ExampleService();
service.setMessage("hello, success!");
return service;
}
}
I coded a #ProfileAll annotation which is very like the standard #Profile annotation, but annotated Configurations, Methods, ... are only processed by spring if ALL of the given profiles are currently active:
ProfileAll.java (Annotation)
import org.springframework.context.annotation.Conditional;
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
#Documented
#Conditional(ProfileAllCondition.class)
public #interface ProfileAll {
/** The set of profiles for which the annotated component should be registered. */
String[] value();
}
ProfileAllCondition.java (Condition)
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;
class ProfileAllCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(ProfileAll.class.getName());
if (attrs != null) {
LinkedList<String> profilesActive = new LinkedList<>();
LinkedList<String> profilesNotActive = new LinkedList<>();
List<Object> values = attrs.get("value");
int count = 0;
for (Object value : values) {
for (String profile : ((String[]) value)) {
count++;
if (context.getEnvironment().acceptsProfiles(profile)) {
profilesActive.add(profile);
} else {
profilesNotActive.add(profile);
}
}
}
if (profilesActive.size() == count) {
return true;
} else {
return false;
}
}
}
return true;
}
}
use it as follows, e.g.:
#Profile({ "mysql", "cloud", "special" })
#Configuration
public class MySqlConfig {
...
MySqlConfig will then only be processed if all three profiles are currently active on startup.

Resources