Search code examples
javaspring-bootdatasourcequartz-scheduler

Connecting quartz datasource to spring boot bean


I have set up a connection to our database, using a bean in spring boot. This all works correctly in our normal application.

@Bean(name="MoliDBConfig")
@Primary
public DataSource dataSource() throws SQLException {

I would like to connect to the same datasource from quartz, but am getting a JNDI error. (As an aside it is worth noting that I have managed to connect to a datasource from quartz by manually providing the config details. See the commented out code in quartz.properties below.)

2019-03-19T10:51:52.342+00:00 [APP/PROC/WEB/0] [OUT] ERROR 2019-03-19 10:51:52.333 - o.q.u.JNDIConnectionProvider 126 Error looking up datasource: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial| at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:662) ~[?:1.8.0_202]| at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:313) ~[?:1.8.0_202]| at javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:350) ~[?:1.8.0_202]| at javax.naming.InitialContext.lookup(InitialContext.java:417) ~[?:1.8.0_202]| at org.quartz.utils.JNDIConnectionProvider.init(JNDIConnectionProvider.java:124) [quartz-2.3.0.jar!/:?]| at org.quartz.utils.JNDIConnectionProvider.(JNDIConnectionProvider.java:102) [quartz-2.3.0.jar!/:?]| at org.quartz.impl.StdSchedulerFactory.instantiate(StdSchedulerFactory.java:995) [quartz-2.3.0.jar!/:?]| at org.quartz.impl.StdSchedulerFactory.getScheduler(StdSchedulerFactory.java:1559) [quartz-2.3.0.jar!/:?]| at com.xxx.d3.moli.schedule.QrtzScheduler.scheduler(QrtzScheduler.java:52) [classes/:?]| at com.xxx.d3.moli.schedule.QrtzScheduler$$EnhancerBySpringCGLIB$$aa50aa7b.CGLIB$scheduler$1() [classes/:?]| at com.xxx.d3.moli.schedule.QrtzScheduler$$EnhancerBySpringCGLIB$$aa50aa7b$$FastClassBySpringCGLIB$$374ea1c1.invoke() [classes/:?]| at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) [spring-core-4.3.22.RELEASE.jar!/:4.3.22.RELEASE]| at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:358) [spring-context-4.3.22.RELEASE.jar!/:4.3.22.RELEASE]| at com.xxx.d3.moli.schedule.QrtzScheduler$$EnhancerBySpringCGLIB$$aa50aa7b.scheduler() [classes/:?]

quartz.properties

# Configure Main Scheduler Properties
org.quartz.scheduler.instanceName = MyClusteredScheduler
org.quartz.scheduler.instanceId = AUTO

# thread-pool
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=2
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true

# job-store
#org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.class = org.springframework.scheduling.quartz.LocalDataSourceJobStore
#org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.dataSource = managedTXDS

org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000


# Configure Datasources
org.quartz.dataSource.managedTXDS.jndiURL=java:comp/env/jdbc/MoliDBConfig

org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver
org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@ldap://oid.xxx.com:389/odsod012,cn=OracleContext,dc=xxx,dc=com
org.quartz.dataSource.myDS.user = MOLI_QRTZ_SCHED
org.quartz.dataSource.myDS.password = MOLI_QRTZ_SCHED
org.quartz.dataSource.myDS.maxConnections = 5
org.quartz.dataSource.myDS.validationQuery=select 0 from dual


# A different classloader is needed to work with Spring Boot dev mode,
# see https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-devtools.html#using-boot-devtools-known-restart-limitations
# and https://github.com/quartz-scheduler/quartz/issues/221
org.quartz.scheduler.classLoadHelper.class=org.quartz.simpl.ThreadContextClassLoadHelper

And my quartz config file

@Configuration
@Profile({"oracle-cloud","mysql-cloud"})
public class QrtzScheduler {

    private static final Logger LOGGER = LogManager.getLogger(QrtzScheduler.class);

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        LOGGER.info("Hello world from Quartz...");
    }

    @Bean
    public SpringBeanJobFactory springBeanJobFactory() {
        AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
        LOGGER.debug("Configuring Job factory");

        jobFactory.setApplicationContext(applicationContext);
        return jobFactory;
    }

    @Bean
    public Scheduler scheduler(Trigger trigger, JobDetail job) throws SchedulerException, IOException {

        StdSchedulerFactory factory = new StdSchedulerFactory();
        factory.initialize(new ClassPathResource("quartz/quartz.properties").getInputStream());

        LOGGER.debug("Getting a handle to the Scheduler");
        Scheduler scheduler = factory.getScheduler();
        scheduler.setJobFactory(springBeanJobFactory());
        if (scheduler.checkExists(job.getKey())){
            scheduler.deleteJob(job.getKey());
        }
        scheduler.scheduleJob(job, trigger);

        LOGGER.debug("Starting Scheduler threads");
        scheduler.start();
        return scheduler;
    }

    @Bean
    public JobDetail jobDetail() {

        return JobBuilder.newJob()
                .ofType(ScheduledJob.class)
                .storeDurably()
                .withIdentity(JobKey.jobKey("Qrtz_Job_Detail"))
                .withDescription("Invoke Sample Job service...")
                .build();
    }

    @Bean
    public Trigger trigger(JobDetail job) {

        int frequencyInMin = 5;
        LOGGER.info("Configuring trigger to fire every {} minutes", frequencyInMin);

        return TriggerBuilder.newTrigger().forJob(job)
                .withIdentity(TriggerKey.triggerKey("Qrtz_Trigger"))
                .withDescription("Sample trigger")
                .withSchedule(simpleSchedule().withIntervalInMinutes(frequencyInMin).repeatForever())
                .build();
    }
}

What is wrong with my approach? (The documentation at quartz-scheduler.org all appears to be down) :-(


Solution

  • So I changed over to this:

    @Configuration
    @Profile({"oracle-cloud","mysql-cloud"})
    public class QrtzScheduler {
    
        private static final Logger LOGGER = LogManager.getLogger(QrtzScheduler.class);
    
        @Autowired
        private ApplicationContext applicationContext;
    
        @Autowired
        @Qualifier("MoliDBConfig")
        private DataSource dataSource;
    
        @Value("${app.repeatInterval}")
        private int repeatInterval;
    
        @Value("${org.quartz.scheduler.instanceName}")
        private String instanceName;
    
        @Value("${org.quartz.scheduler.instanceId}")
        private String instanceId;
    
        @Value("${org.quartz.threadPool.threadCount}")
        private String threadCount;
    
        @Value("${org.quartz.threadPool.class}")
        private String threadClass;
    
        @Value("${org.quartz.threadPool.threadPriority}")
        private String threadPriority;
    
        @Value("${org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread}")
        private String threadsInheritContextClassLoaderOfInitializingThread;
    
        @Value("${org.quartz.jobStore.class}")
        private String jobStoreClass;
    
        @Value("${org.quartz.jobStore.driverDelegateClass}")
        private String jobStoreDriverDelegateClass;
    
        @Value("${org.quartz.jobStore.useProperties}")
        private String jobStoreUseProperties;
    
        @Value("${org.quartz.jobStore.tablePrefix}")
        private String jobStoreTablePrefix;
    
        @Value("${org.quartz.jobStore.misfireThreshold}")
        private String jobStoreMisfireThreshold;
    
        @Value("${org.quartz.jobStore.isClustered}")
        private String jobStoreIsClustered;
    
        @Value("${org.quartz.jobStore.clusterCheckinInterval}")
        private String jobStoreClusterCheckinInterval;
    
        @Value("${org.quartz.scheduler.classLoadHelper.class}")
        private String schedulerClassLoadHelperClass;
    
        @PostConstruct
        public void init() {
            LOGGER.info("Hello world from Quartz...");
        }
    
        @Bean
        public SpringBeanJobFactory jobFactory() {
            AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
            LOGGER.debug("Configuring Job factory");
    
            jobFactory.setApplicationContext(applicationContext);
            return jobFactory;
        }
    
        @Bean
        public SchedulerFactoryBean schedulerFactoryBean() {
    
            LOGGER.debug("Configuring schedulerFactoryBean");
            SchedulerFactoryBean factory = new SchedulerFactoryBean();
    
            factory.setOverwriteExistingJobs(true);
            factory.setJobFactory(jobFactory());
    
            Properties quartzProperties = new Properties();
            quartzProperties.setProperty("org.quartz.scheduler.instanceName",instanceName);
            quartzProperties.setProperty("org.quartz.scheduler.instanceId",instanceId);
            quartzProperties.setProperty("org.quartz.threadPool.threadCount",threadCount);
            quartzProperties.setProperty("org.quartz.threadPool.class",threadClass);
            quartzProperties.setProperty("org.quartz.threadPool.threadPriority",threadPriority);
            quartzProperties.setProperty("org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread",threadsInheritContextClassLoaderOfInitializingThread);
            quartzProperties.setProperty("org.quartz.jobStore.class",jobStoreClass);
            quartzProperties.setProperty("org.quartz.jobStore.driverDelegateClass",jobStoreDriverDelegateClass);
            quartzProperties.setProperty("org.quartz.jobStore.useProperties",jobStoreUseProperties);
            quartzProperties.setProperty("org.quartz.jobStore.tablePrefix",jobStoreTablePrefix);
            quartzProperties.setProperty("org.quartz.jobStore.misfireThreshold",jobStoreMisfireThreshold);
            quartzProperties.setProperty("org.quartz.jobStore.isClustered",jobStoreIsClustered);
            quartzProperties.setProperty("org.quartz.jobStore.clusterCheckinInterval",jobStoreClusterCheckinInterval);
            quartzProperties.setProperty("org.quartz.scheduler.classLoadHelper.class",schedulerClassLoadHelperClass);
    
            factory.setDataSource(dataSource);
    
            factory.setQuartzProperties(quartzProperties);
            factory.setTriggers(moliJobTrigger().getObject());
    
            return factory;
        }
    
        @Bean(name = "moliJobTrigger")
        public SimpleTriggerFactoryBean moliJobTrigger() {
            long minute = 60000;
            long repeatIntervalInMin = repeatInterval * minute;
            LOGGER.debug("Configuring jobTrigger");
    
            SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
            factoryBean.setJobDetail(moliJobDetails().getObject());
            factoryBean.setStartDelay(minute);
            factoryBean.setRepeatInterval(repeatIntervalInMin);
            factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
            factoryBean.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);
            return factoryBean;
        }
    
        @Bean(name = "moliJobDetails")
        public JobDetailFactoryBean moliJobDetails() {
    
            LOGGER.debug("Configuring jobDetails");
            JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
            jobDetailFactoryBean.setJobClass(ScheduledAutomaticMonitoringJob.class);
            jobDetailFactoryBean.setDescription("Moli_Quartz_Description");
            jobDetailFactoryBean.setDurability(true);
            jobDetailFactoryBean.setName("Moli_Quartz_Name");
    
            return jobDetailFactoryBean;
        }
    }
    

    application.yml

    org:
      quartz:
        scheduler:
          instanceName: MyClusteredScheduler
          instanceId: AUTO
          classLoadHelper:
            class: org.quartz.simpl.ThreadContextClassLoadHelper
        threadPool:
          class: org.quartz.simpl.SimpleThreadPool
          threadCount: 10
          threadPriority: 5
          threadsInheritContextClassLoaderOfInitializingThread: true
        jobStore:
          class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
          dataSource: app.dataSource
          driverDelegateClass: org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
          useProperties: false
          tablePrefix: COT_QRTZ_
          misfireThreshold: 60000
          isClustered: true
          clusterCheckinInterval: 20000
    

    Also needed this class:

    import org.quartz.spi.TriggerFiredBundle;
    import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.scheduling.quartz.SpringBeanJobFactory;
    
    /**
     * Adds auto-wiring support to quartz jobs.
     */
    public final class AutoWiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
    
        private AutowireCapableBeanFactory beanFactory;
    
        public void setApplicationContext(ApplicationContext applicationContext) {
    
            beanFactory = applicationContext.getAutowireCapableBeanFactory();
        }
    
        @Override
        protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
    
            final Object job = super.createJobInstance(bundle);
            beanFactory.autowireBean(job);
            return job;
        }
    
    }