javacdideltaspike

DeltaSpike custom ConfigSource with CDI


I am trying to define a custom DeltaSpike ConfigSource. The custom config source will have the highest priority and check the database for the config parameter.

I have a ConfigParameter entity, that simply has a key and a value.

@Entity
@Cacheable
public class ConfigParameter ... {

      private String key;
      private String value;

}

I have a @Dependent DAO that finds all config parameters.

What I am trying to do now, is define a custom ConfigSource, that is able to get the config parameter from the database. Therefore, I want to inject my DAO in the ConfigSource. So basically something like

@ApplicationScoped
public class DatabaseConfigSource implements ConfigSource {

    @Inject
    private ConfigParameterDao configParameterDao;

    ....
}

However, when registering the ConfigSource via META-INF/services/org.apache.deltaspike.core.spi.config.ConfigSource, the class will be instantiated and CDI will not work.

Is there any way to get CDI working in this case?

Thanks in advance, if you need any further information, please let me know.


Solution

  • The main problem is, that the ConfigSource gets instantiated very early on when the BeanManager is not available yet. Even the JNDI lookup does not work at that point in time. Thus, I need to delay the injection/lookup.

    What I did now, is add a static boolean to my config source, that I set manually. We have a InitializerService that makes sure that the system is setup properly. At the end of the initialization process, I call allowInitialization() in order to tell the config source, that the bean is injectable now. Next time the ConfigSource is asked, it will be able to inject the bean using BeanProvider.injectFields.

    public class DatabaseConfigSource implements ConfigSource {
    
        private static boolean allowInit;
    
        @Inject
        private ConfigParameterProvider configParameterProvider;
    
        @Override
        public int getOrdinal() {
            return 500;
        }
    
        @Override
        public String getPropertyValue(String key) {
            initIfNecessary();
    
            if (configParameterProvider == null) {
                return null;
            }
    
            return configParameterProvider.getProperty(key);
        }
    
        public static void allowInitialization() {
            allowInit = true;
        }
    
        private void initIfNecessary() {
            if (allowInit) {
                BeanProvider.injectFields(this);
            }
        }
    
    }
    

    I have a request-scoped bean that holds all my config variables for type-safe access.

    @RequestScoped
    public class Configuration {
    
        @Inject
        @ConfigProperty(name = "myProperty")
        private String myProperty;
    
        @Inject
        @ConfigProperty(name = "myProperty2")
        private String myProperty2;
    
        ....
    
    }
    

    When injecting the Configuration class in a different bean, each ConfigProperty will be resolved. Since my custom DatabaseConfigSource has the highest ordinal (500), it will be used for property resolution first. If the property is not found, it will delegate the resolution to the next ConfigSource.

    For each ConfigProperty the getPropertyValue function from the DatabaseConfigSource is called. Since I do not want to retreive the parameters from the database for each config property, I moved the config property resolution to a request-scoped bean.

    @RequestScoped
    public class ConfigParameterProvider {
    
        @Inject
        private ConfigParameterDao configParameterDao;
    
        private Map<String, String> configParameters = new HashMap<>();
    
        @PostConstruct
        public void init() {
            List<ConfigParameter> configParams = configParameterDao.findAll();
            configParameters = configParams.stream()
                .collect(toMap(ConfigParameter::getId, ConfigParameter::getValue));
        }
    
        public String getProperty(String key) {
            return configParameters.get(key);
        }
    
    }
    

    I could sure change the request-scoped ConfigParameterProvider to ApplicationScoped. However, we have a multi-tenant setup and the parameters need to be resolved per request.

    As you can see, this is a bit hacky, because we need to explicitly tell the ConfigSource, when it is allowed to be instantiated properly (inject the bean).

    I would prefer a standarized solution from DeltaSpike for using CDI in a ConfigSource. If you have any idea on how to properly realise this, please let me know.