Search code examples
javadatasourcecdiquarkusmicroprofile

Custom ConfigSource for Quarkus


I'm trying now to configure custom ConfigSource in my Quarkus App. Like in many other manuals i'm created my own DatabaseSourceConfig and implements org.eclipse.microprofile.config.spi.ConfigSource interface.I registered my ConfigSource in:

/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource

There is my ConfigSource:

public class DatabaseConfigSource implements ConfigSource {
private DataSource dataSource;

public DatabaseConfigSource() {
    try {
        dataSource = (DataSource) new InitialContext().lookup("openejb:Resource/config-source-database");
    } catch (final NamingException e) {
        throw new IllegalStateException(e);
    }
}

@Override
public Map<String, String> getProperties() {
    // Implementing Method
}

@Override
public String getValue(final String propertyName) {
    // Implementing Method
}

@Override
public String getName() {
    return DatabaseConfigSource.class.getSimpleName();
}

}

But this not working for Quarkus because of JNDI name. I need to use CDI. I was trying to use something like this:

@Inject
@io.quarkus.agroal.DataSource("my_connection")
AgroalDataSource usersDataSource;

and declare this connection in application.properties but it didn't help me. I'm getting all the time NULL Exception. Maybe someone have ideas, how can i get DB connection there without to use JNDI namespace?


Solution

  • I found some answer myself, maybe it will be useful also for other ppl. Like @Janmartiška said, CDI booted later, than ConfigSource, that's why i don't see any way to inject my connection via CDI. I was created some HibernateUtil Class:

    package org.myproject.config;
    
    import java.util.Properties;
    
    import org.hibernate.SessionFactory;
    import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
    import org.hibernate.cfg.Configuration;
    import org.hibernate.service.ServiceRegistry;
    import org.myproject.entities.ConfigurationsEntity;
    
    public class HibernateUtil {
        private static SessionFactory sessionFactory;
    
        private static SessionFactory buildSessionFactory() {
            try {
                Properties props = new Properties();
                props.setProperty("hibernate.connection.url", "jdbc:mysql://[db-host]:[db-port]/db_name");
                props.setProperty("hibernate.connection.driver_class", "com.mysql.cj.jdbc.Driver");
                props.setProperty("hibernate.connection.username", "username");
                props.setProperty("hibernate.connection.password", "password");
    
                props.setProperty("hibernate.current_session_context_class", "thread");
                props.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
    
                Configuration configuration = new Configuration();
                configuration.addProperties(props);
                configuration.addAnnotatedClass(ConfigurationsEntity.class);
                System.out.println("Hibernate Configuration loaded");
    
                ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
                System.out.println("Hibernate serviceRegistry created");
    
                SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
    
                return sessionFactory;
            }
            catch (Throwable ex) {
                // Make sure you log the exception, as it might be swallowed
                System.err.println("Initial SessionFactory creation failed." + ex);
                throw new ExceptionInInitializerError(ex);
            }
        }
    
        public static SessionFactory getSessionFactory() {
            if(sessionFactory == null) sessionFactory = buildSessionFactory();
            return sessionFactory;
        }
    
    }
    

    than i used it in my SourceConfig:

    package org.myproject.config;
    
    import io.quarkus.runtime.annotations.RegisterForReflection;
    import org.eclipse.microprofile.config.spi.ConfigSource;
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.myproject.entities.ConfigurationsEntity;
    
    import javax.persistence.NoResultException;
    import javax.persistence.Query;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @RegisterForReflection
    public class DatabaseSourceConfig implements ConfigSource {
    
        public SessionFactory sessionFactory;
        public Session currentSession;
    
        public DatabaseSourceConfig() {
    
            sessionFactory = HibernateUtil.getSessionFactory();
            this.checkFactoryConnection();
    
        }
    
        public void checkFactoryConnection() {
    
            if (currentSession == null || (currentSession != null && !currentSession.isOpen())) {
                try {
                    currentSession = sessionFactory.getCurrentSession();
                } catch (NullPointerException e) {
                    currentSession = sessionFactory.openSession();
                }
            }
    
        }
    
        @Override
        public Map<String, String> getProperties() {
            // Implementing Method
        }
    
        @Override
        public String getValue(String propertyName) {
                this.checkFactoryConnection();
                ConfigurationsEntity conf = new ConfigurationsEntity();
                currentSession.beginTransaction();
    
                try {
                    Query query = currentSession.createNamedQuery("Configuration.selectOne", ConfigurationsEntity.class);
                    query.setParameter("name", propertyName);
                    conf = (ConfigurationsEntity) query.getSingleResult();
                    currentSession.getTransaction().commit();
    
                } catch (Exception ex) {
                    currentSession.getTransaction().rollback();
                }
    
                return conf.getValue();
    
        }
    
        @Override
        public String getName() {
            return DatabaseSourceConfig.class.getSimpleName();
        }
    }
    

    Now i can use my ConfigSource in other classes like:

    @Inject
    @ConfigProperty(name = "[property-name-like-in-db]")
    public String someProperty;
    

    After my further research it was found that ConfigSource has no access to CDi and application.properties. That is why there is nothing left but to establish a connection to the database in the manner described above. However, I did a little editing of the example. I cached properties from the database and created a @ApplicationScoped Bean that looks into the database once every 5 minutes to see whether one of properties "updated_at" has a timestamp later than others from Bean loaded properties.

    However, I have to say that according to Quarkus and Apache developers - this violates “immutable deployment” and is not planned to change the application settings during runtime. So it depends on you whether you write it in the app or not.