I have the following problem, I have a certain configuration class in spring boot which contains beans, which I would like to be created only if a certain property which has a list of values, contains a certain value.

now the configuration class looks something like this:

@ConditionalOnExpression("#{'${conditions.values.options}'.contains('ScenarioOne')}")
ConfigurationForScenarioOne{

  @Bean
  public StudentBean getStudentBean(){
  ...
  }
}

In the properties file I would have something like so:

conditions.values.options=${CONDITIONS_VALUES_OPTIONS}

Then provide the value of CONDITIONS_VALUES_OPTIONS at runtime like so:

-DCONDITIONS_VALUES_OPTIONS=ScenarioOne,ScenarioTwo,ScenarioFive

The reason why I want to do this is to be able to deploy the app and have different beans be in use depending on the value given at runtime.

I will have several of these Configuration classes that will be created based on which ones of these properties are passed. TO achieve this I was trying to rely on Spring Expression language to do the following: 1-Read the property conditions.values.options, then convert it to a list, then verify if the list contained the desired configuration string.

So far I have tried several expressions including:

@ConditionalOnExpression("#{'${conditions.values.options}'.contains('ScenarioOne')}")

and other similar expressions, with no luck. Can someone help me see what I am missing here?

3

There are 3 best solutions below

0
On

I was able to get the desired behavior using this:

@ConditionalOnExpression("#{T(java.util.Arrays).asList('${conditions.values.options}').contains('ScenarioOne')}")

So dissecting this a little bit in the hopes to help others who may come across similar problems: It seems that spring reads comma separated properties as an Array, this is bogus because the following can be done using spring boot:

The following two statements are valid:

@Value("${property.with.comma.separated.values}")
private List<String> prop;

OR

@Value("${property.with.comma.separated.values}")
private String [] prop;

now the expression:

"#{'${conditions.values.options}'.contains('ScenarioOne')}"

will throw an error saying that the Array class does not contain a method called '.contains()' hence this is why I had to resort to use some method execution:

T(java.util.Arrays).asList('${conditions.values.options}')

to read the comma separated values, convert the String array into a List and then perform the '.contains(..)' method invocation.

I wish there was a simpler way to do it, to have to do a method invocation in there seems like overkill.

I am open to sugestions

1
On

Can you try writing your annotation values like this and check :

@ConditionalOnExpression("#{${conditions.values.options}}.contains('ScenarioOne')")

Write the contains method after the curly brackets.

0
On

An alternative is to implement a custom conditional annotation.

@ConditionalOnHavingValue(property = "conditions.values.options", value = "ScenarioOne")

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnHavingValueCondition.class)
public @interface ConditionalOnHavingValue {

    /**
     * The property that should contain the value.
     */
    String property();

    /**
     * The value that must be present.
     */
    String value();

}
public class OnHavingValueCondition extends SpringBootCondition {

    @Override
    public ConditionOutcome getMatchOutcome(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
        final MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(ConditionalOnHavingValue.class.getName());
        if (attributes == null) {
            return ConditionOutcome.noMatch("Unable to retrieve attributes from ConditionalOnHavingValue annotation");
        }

        final String property = (String) attributes.getFirst("property");
        final String value = (String) attributes.getFirst("value");

        //noinspection unchecked,DataFlowIssue
        final Set<String> propertyValues = (Set<String>) context.getEnvironment().getProperty(property, Set.class);

        //noinspection DataFlowIssue
        if (!propertyValues.contains(value)) {
            return ConditionOutcome.noMatch("Unable to find the particular value in the property");
        }

        return ConditionOutcome.match();
    }

}