4

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.

kevcodez
  • 1,261
  • 12
  • 27

3 Answers3

1

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.

kevcodez
  • 1,261
  • 12
  • 27
  • You can still improve `initIfNecessary()` - at the moment, it works rather like `initIfPossible()`, i.e. the fields don't get injected too soon, but they do get injected on every invocation of `getPropertyValue()`. – Harald Wellmann May 20 '16 at 15:27
  • The 1.8 release will be fixing this. – John Ament Nov 27 '16 at 15:35
  • You might be able to trigger the setup once the ApplicationScoped is set up. Write a method ```private void initDatabaseConfigSource(@Observes @Initialized(ApplicationScoped.class) Object init){ this.doInitStuff(); } ``` That way the BeanMangere etc should be ready to use. – Benjamin Jan 08 '19 at 11:08
1

Even though this post has been answered already I'd like to suggest another possible solution for this problem.

I managed to load properties from my db service by creating an @Signleton @Startup EJB which extends the org.apache.deltaspike.core.impl.config.BaseConfigSource and injects my DAO as delegate which I then registered into the org.apache.deltaspike.core.api.config.ConfigResolver.

@Startup
@Singleton
public class DatabaseConfigSourceBean extends BaseConfigSource {

    private static final Logger logger = LoggerFactory.getLogger(DatabaseConfigSourceBean.class);

    private @Inject PropertyService delegateService;

    @PostConstruct
    public void onStartup() {
        ConfigResolver.addConfigSources(Collections.singletonList(this));
        logger.info("Registered the DatabaseConfigSourceBean in the ConfigSourceProvider ...");
    }

    @Override
    public Map<String, String> getProperties() {
        return delegateService.getProperties();
    }

    @Override
    public String getPropertyValue(String key) {
        return delegateService.getPropertyValue(key);
    }

    @Override
    public String getConfigName() {
        return DatabaseConfigSourceBean.class.getSimpleName();
    }

    @Override
    public boolean isScannable() {
        return true;
    }
}

I know that creating an EJB for this purpose basically produces a way too big overhead, but I think it's a bit of a cleaner solution instead of handling this problem by some marker booleans with static accessors ...

downdrown
  • 341
  • 1
  • 3
  • 14
0

DS is using the java se spi mechanism for this which is not CD'Injectable'. One solution would be to use the BeanProvider to get hold of your DatabaseConfigSource and delegate operations to it.

Franck
  • 1,754
  • 1
  • 13
  • 14
  • The ConfigSources get initialized before the beanprovider is initialized. Thus, when trying to use the Beanprovider in the ConfigSource an exception is thrown, that there is no provider available. – kevcodez May 18 '16 at 14:04
  • Ok but if you access BeanProvider in getPropery/getProperties methods isn't it fine? Are those 2 properties called on init ? – Franck May 18 '16 at 14:32
  • The `getProperty` function is called during different stages of application/container startup. DS tries getting quite a few internal properties first, before trying to resolve my custom config properties. During the first "run-through", the BeanProvider is not available. The `getProperties` function is not called at all. I need the ConfigSource or anything similiar to be initialized when the CDI beans are done initializing. – kevcodez May 18 '16 at 15:05