PentaKon
PentaKon

Reputation: 4646

Using @Conditional Spring annotation without knowing the whole property name

I want to optionally enable a @Configuration, based on whether a type of application.properties property exists or not. However I will not know the whole property name in advance, only part of its prefix i.e.

spring.security.oauth2.<provider-name>.url=x.y.z

And the goal is to enable the configuration using something like:

@ConditionalOnProperty(prefix = "spring.security", value="oauth2.*")

or something like that but it doesn't seem to work. I've tried multiple combinations, none work it always expects the full property name but I don't know the <provider-name> in advance. Any ways to achieve this functionality?

Upvotes: 4

Views: 1804

Answers (2)

Sandeep Poonia
Sandeep Poonia

Reputation: 2188

To improve on @Pentakon answer, few more additions that we can do:

  1. Create our own annotation to hold the prefix

     @Target({ElementType.METHOD, ElementType.TYPE})
     @Retention(RetentionPolicy.RUNTIME) 
     public @interface PropertyPrefix {
    
         String value(); 
     }
    
  2. Use this on in your configuration class

     @PropertyPrefix("redis")
     @Conditional(PropertyPrefixPresentCondition.class)
    
  3. And the implementation of PropertyPrefixPresentCondition

    public class PropertyPrefixPresentCondition implements Condition {
    
      @Override
      public boolean matches(@NotNull ConditionContext context, AnnotatedTypeMetadata metadata) {
          var prefix = metadata.getAnnotations()
             .get(PropertyPrefix.class)
             .getValue("value")
             .map(Object::toString)
             .orElse(null);
          if (StringUtils.isEmpty(prefix)) {
              throw new IllegalStateException("Prefix must be present");
          }
          var env = context.getEnvironment();
          for (PropertySource<?> source : ((AbstractEnvironment) env).getPropertySources()) {
              if (source instanceof MapPropertySource mapPropertySource) {
                  for (String propertyName : mapPropertySource.getSource().keySet()) {
                      if (propertyName.startsWith(prefix)) {
                          return true;
                      }
                  }
              }
          }
          return false;
      }
    }
    

Upvotes: 0

PentaKon
PentaKon

Reputation: 4646

As of right now Spring does not support the requested functionality with the out of the box annotations which means a custom @Conditional condition must be created that does what we want. The Condition implementation looks like this:

public class EnableOAuth2Condition implements Condition {
  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    Environment env = context.getEnvironment();
    for(Iterator it = ((AbstractEnvironment) env).getPropertySources().iterator(); it.hasNext(); ) {
      PropertySource propertySource = (PropertySource) it.next();
      if (propertySource instanceof MapPropertySource) {
        for (String propertyName : ((MapPropertySource) propertySource).getSource().keySet()) {
          if(propertyName.startsWith("spring.security.oauth2")) {
            return true;
          }
        }
      }
    }
    return false;
  }
}

and the way to use it on a @Configuration class is the following:

@Configuration
@Conditional(EnableOAuth2Condition.class)
public class MyOAuth2Config {
  ...
}

A bit messy for something simple but that's the way to go for now.

Upvotes: 3

Related Questions