Search code examples
javaspringquartz

Spring Quartz remote scheduler


I started quartz server instance using spring (as in article), it works good. But when I try to start another app with quartz client and connect to quartz server, I have an error, please help what I'm doing wrong.

Server config:

@Configuration
public class ServerConfig {
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(ApplicationContext applicationContext) {
        SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
        schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
        schedulerFactory.setSchedulerListeners(...);
        schedulerFactory.setJobFactory(...);
        schedulerFactory.setTriggers(...);
        return schedulerFactory;
    }
    ...

}

Client config:

@Configuration
public class ClientConfig {
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
        schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
        return schedulerFactory;
    }
...
}

Server quartz.properties:

org.quartz.scheduler.instanceName=myScheduler
org.quartz.scheduler.rmi.export=true
org.quartz.scheduler.rmi.createRegistry=true
org.quartz.scheduler.rmi.registryHost=localhost
org.quartz.scheduler.rmi.registryPort=1099
org.quartz.scheduler.rmi.serverPort=1100

Client quartz.properties:

org.quartz.scheduler.instanceName=myScheduler
org.quartz.scheduler.rmi.proxy=true
org.quartz.scheduler.rmi.registryHost=localhost
org.quartz.scheduler.rmi.registryPort=1099

Server logs:

[2019-09-03] [10:07:26.171] INFO  SchedulerFactoryBean:538 - Loading Quartz config from [class path resource [quartz.properties]]
[2019-09-03] [10:07:26.229] INFO  StdSchedulerFactory:1208 - Using default implementation for ThreadExecutor
[2019-09-03] [10:07:26.272] INFO  SchedulerSignalerImpl:61 - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
[2019-09-03] [10:07:26.273] INFO  QuartzScheduler:229 - Quartz Scheduler v.2.3.0 created.
[2019-09-03] [10:07:26.277] INFO  RAMJobStore:155 - RAMJobStore initialized.
[2019-09-03] [10:07:26.338] INFO  QuartzScheduler:421 - Scheduler bound to RMI registry under name 'schedulerFactoryBean_$_NON_CLUSTERED'
[2019-09-03] [10:07:26.343] INFO  QuartzScheduler:294 - Scheduler meta-data: Quartz Scheduler (v2.3.0) 'schedulerFactoryBean' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - access via RMI.
...
[2019-09-03] [10:07:26.344] INFO  StdSchedulerFactory:1362 - Quartz scheduler 'schedulerFactoryBean' initialized from an externally provided properties instance.
[2019-09-03] [10:07:26.345] INFO  StdSchedulerFactory:1366 - Quartz scheduler version: 2.3.0
...
[2019-09-03] [10:07:29.12] INFO  SchedulerFactoryBean:684 - Starting Quartz Scheduler now
[2019-09-03] [10:07:29.13] INFO  QuartzScheduler:547 - Scheduler schedulerFactoryBean_$_NON_CLUSTERED started.

Client logs:

[2019-09-03] [10:11:53.255] INFO  SchedulerFactoryBean:538 - Loading Quartz config from [class path resource [quartz.properties]]
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'schedulerFactoryBean' defined in class path resource [com/mypackage/ClientConfig.class]: Invocation of init method failed; nested exception is org.quartz.SchedulerException: Operation not supported for remote schedulers.
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1631)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:481)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:742)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
    at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:73)
    ... 3 more
Caused by: org.quartz.SchedulerException: Operation not supported for remote schedulers.
    at org.quartz.impl.RemoteScheduler.getListenerManager(RemoteScheduler.java:913)
    at org.springframework.scheduling.quartz.SchedulerAccessor.registerListeners(SchedulerAccessor.java:340)
    at org.springframework.scheduling.quartz.SchedulerFactoryBean.afterPropertiesSet(SchedulerFactoryBean.java:476)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1689)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1627)
    ... 13 more

As you can see, the error is: org.quartz.SchedulerException: Operation not supported for remote schedulers. and happens while bean initialization. What's wrong with it?

After client fails to start, server shows in logs:

[2019-09-03] [10:11:53.462] INFO  QuartzScheduler:666 - Scheduler schedulerFactoryBean_$_NON_CLUSTERED shutting down.
[2019-09-03] [10:11:53.462] INFO  QuartzScheduler:585 - Scheduler schedulerFactoryBean_$_NON_CLUSTERED paused.
[2019-09-03] [10:11:53.871] INFO  QuartzScheduler:447 - Scheduler un-bound from name 'schedulerFactoryBean_$_NON_CLUSTERED' in RMI registry
[2019-09-03] [10:11:53.872] INFO  QuartzScheduler:740 - Scheduler schedulerFactoryBean_$_NON_CLUSTERED shutdown complete.

I have versions of Spring - 4.3.20, Quartz - 2.3.0

Update: I see registerListeners() call in SchedulerFactoryBean:

@Override
public void afterPropertiesSet() throws Exception {
        ...
        try {
            registerListeners();
            registerJobsAndTriggers();
        }
        catch (Exception ex) {
            try {
                this.scheduler.shutdown(true);
            }
            catch (Exception ex2) {
                logger.debug("Scheduler shutdown exception after registration failure", ex2);
            }
            throw ex;
        }
}

and in registerListeners() we call getScheduler().getListenerManager() which fails with Operation not supported for remote schedulers exception. How to avoid calling registerListeners() for RemoteScheduler?


Solution

  • Finally I get it working the following way:

    • I refused from using Spring's SchedulerFactoryBean on a client, but instead I directly create an instance of StdSchedulerFactory from quartz library (then it properly creates an instance of RemoteScheduler): new StdSchedulerFactory(). It properly connects to a server quartz instance.

    • Due to I cannot obtain an instance of StdSchedulerFactory from SchedulerFactoryBean I just pass both to beans where it is needed. So on a client app SchedulerFactoryBean is null, and on a server app StdSchedulerFactory is null.

    • Job schedule part is the same for both factories as all I do with them is obtain scheduler:

    private Scheduler getScheduler() throws SchedulerException {
        if (schedulerFactoryBean != null) return schedulerFactoryBean.getScheduler();
        if (schedulerFactory != null) return schedulerFactory.getScheduler();
        else return null;
    }
    

    and then...

    Scheduler scheduler = getScheduler();
    if (scheduler != null) {
        scheduler.scheduleJob(job, trigger);
    }