spring integration + cron + quartz in cluster? - spring

I have a spring integration flow triggered by the cron expression like follows:
<int-ftp:inbound-channel-adapter id="my-input-endpoint" ...>
<int:poller trigger="my-trigger"/>
</int-ftp:inbound-channel-adapter>
<bean id="my-trigger"
class="org.springframework.scheduling.support.CronTrigger">
<constructor-arg value="0 * * * * *" />
</bean>
It works fine. But now I have to extend the implementation to make it cluster ready (job execution on only one cluster node at the same point of time).
My wish would be to use the Quartz framework in the cluster mode (persisting the job status in the database) to trigger this integration flow. Quartz provides a beautful solution out of the box. The only problem is how to integrate the Quartz with the existing inbout-channer-adaptor? The "trigger" attribute of the "poller" accepts only the subclasses of the org.springframework.scheduling.Trigger. I could not find any bridge between "poller trigger" and the Quartz framework.
many thanks in advance!

Here's one way...
Set the auto-startup attribute on the inbound-adapter to false.
Create a custom trigger that only fires once, immediately...
public static class FireOnceTrigger implements Trigger {
boolean done;
public Date nextExecutionTime(TriggerContext triggerContext) {
if (done) {
return null;
}
done = true;
return new Date();
}
public void reset() {
done = false;
}
}
In your quartz job, get a reference to the trigger and the SourcePollingChannelAdapter.
When the quartz trigger fires, have the quartz job
adapter.stop()
trigger.reset()
adapter.start()

the solution from Gary works. This my spring context:
<int-ftp:inbound-channel-adapter id="my-endpoint"
auto-startup="false">
<int:poller trigger="my-endpoint-trigger"/>
</int-ftp:inbound-channel-adapter>
<bean id="my-endpoint-trigger" class="com.my.FireOnceTrigger"/>
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="my-job-trigger" />
</list>
</property>
<property name="schedulerContextAsMap">
<map>
<entry key="inputEndpoint"><ref bean="my-input-endpoint" /></entry>
<entry key="inputEndpointTrigger"><ref bean="my-endpoint-trigger" /></entry>
</map>
</property>
</bean>
<bean id="my-job-trigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="cronExpression" value="0 * * * * ?" />
<property name="jobDetail" ref="my-job" />
</bean>
<bean name="my-job" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="com.my.MyActivatorJob " />
</bean>
and MyActivatorJob class:
public class MyActivatorJob extends QuartzJobBean implements {
private AbstractEndpoint inputEndpoint;
private FireOnceTrigger inputEndpointTrigger;
public void setInputEndpoint(final AbstractEndpoint pInputEndpoint) {
this.inputEndpoint = pInputEndpoint;
}
public void setInputEndpointTrigger(final FireOnceTrigger pInputEndpointTrigger) {
this.inputEndpointTrigger = pInputEndpointTrigger;
}
#Override
protected void executeInternal(final JobExecutionContext pParamJobExecutionContext)
throws JobExecutionException {
inputEndpoint.stop();
inputEndpointTrigger.reset();
inputEndpoint.start();
}
}
As a next step this spring context would have to be refactored to replace the usage of schedulerContextAsMap with something more flexible and be able to define more jobs activating and deactivating many different endpoints.
Thanks Gary so far!

tried to integrate the quartz and spring as you proposed but faced two other problems:
1.) IncompatibleClassChangeError exception when using Quartz 2.x and Spring 3.x. It is a known problem but I did not find any solution for that.
2.) Injection of other spring bean into the Quarz job instance. I found some solutions but no one works for me. I've tried the one with using
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobFactory">
<bean class="org.springframework.scheduling.quartz.SpringBeanJobFactory" />
</property>
<property name="triggers">
...
</property>
<property name="schedulerContextAsMap">
<map>
<entry key="inputEndpoint" value-ref="my-endpoint" />
</map>
</property>
</bean>
to inject other beans into the job but after adding this property into the SchedulerFactoryBean the jobs is not being executed (and I dont see any exception). Removing the property "schedulerContextAsMap" out makes the job running again.

I haven't tried it but see that the Quartz 2 and Spring compatibility issues seem to have been fixed in Spring 3.1.1. See https://jira.springsource.org/browse/SPR-8889

Related

spring quartz doesn't work

I've got some controller, which performs some task and Tomcat. It works fine.
I want some job that will work every 5 seconds during its work.
I've created xml file quartz-config.xml with data
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="someTrigger"/>
</list>
</property>
<property name="jobDetails">
<list>
<ref bean="someJob" />
</list>
</property>
</bean>
<bean id="someTrigger"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="someJob"/>
<property name="cronExpression" value="0 0/5 * * * ?" />
</bean>
<bean id="someJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="name" value="someJob" />
<property name="jobClass" value="com.service.scheduler.SomeJob" />
</bean>
and added path to xml file in my spring-context.xml.
I have created class
public class SomeJob extends QuartzJobBean {
public void execute(JobDataMap jobDataMap) throws JobExecutionException {
executeInternal(null);
}
#Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("SCHEDULER WORKS!");
}
}
But it doesn't do anything in console. I am only getting messages batch acquisition of 0 triggers.
Why? What's the problem?
Check quart web page.
org.quartz.scheduler.batchTriggerAcquisitionMaxCount
The maximum number of triggers that a scheduler node is allowed to acquire (for firing) at once. Default value is 1. The larger the number, the more efficient firing is (in situations where there are very many triggers needing to be fired all at once) - but at the cost of possible imbalanced load between cluster nodes.
http://quartz-scheduler.org/documentation/quartz-2.x/configuration/ConfigMain][1]

Can I turn off quartz scheduler with a property setting?

We disable the quartz scheduler locally by commenting out the scheduler factory bean in the jobs.xml file.
Is there a setting for doing something similar in the quartz.properties file?
If you use Spring Framework you can make subclass from org.springframework.scheduling.quartz.SchedulerFactoryBean and override afterPropertiesSet() method.
public class MySchedulerFactoryBean extends org.springframework.scheduling.quartz.SchedulerFactoryBean {
#Autowired
private #Value("${enable.quartz.tasks}") boolean enableQuartzTasks;
#Override
public void afterPropertiesSet() throws Exception {
if (enableQuartzTasks) {
super.afterPropertiesSet();
}
}
}
Then change declaration of factory in xml file and set "enable.quartz.tasks" property in properties file. That's all.
Of course, instead using #Autowired you can write and use setter method and add
<property name="enableQuartzTasks" value="${enable.quartz.tasks}"/>
to MySchedulerFactoryBean declaration in xml.
No. But the properties file doesn't start the scheduler.
The scheduler doesn't start until/unless some code invokes scheduler.start().
It seems that there is a property autoStartup in org.springframework.scheduling.quartz.SchedulerFactoryBean. So you can configure it in XML config like this:
<bean id="quartzFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="autoStartup" value="${cron.enabled}"/>
<property name="triggers">
<list>
<ref bean="someTriggerName"/>
</list>
</property>
</bean>
Thanks to https://chrisrng.svbtle.com/configure-spring-to-turn-quartz-scheduler-onoff
You can disable Quartz Scheduler if you use Spring Framework 3.1 for creating and starting it.
On my Spring configuration file I use the new profiles feature of Spring 3.1 in this way:
<beans profile="production,test">
<bean name="bookingIndexerJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.xxx.indexer.scheduler.job.BookingIndexerJob" />
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="10" />
</map>
</property>
</bean>
<bean id="indexerSchedulerTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="jobDetail" ref="bookingIndexerJob" />
<property name="startDelay" value="1000" />
<property name="repeatInterval" value="5000" />
</bean>
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="indexerSchedulerTrigger" />
</list>
</property>
<property name="dataSource" ref="ds_quartz-scheduler"></property>
<property name="configLocation" value="classpath:quartz.properties" />
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
</bean>
</beans>
Only when I want to start the Scheduler (for example on the production environment), I set the spring.profiles.active system property, with the list of active profiles:
-Dspring.profiles.active="production"
More info here:
http://blog.springsource.com/2011/02/11/spring-framework-3-1-m1-released/
http://java.dzone.com/articles/spring-profiles-or-not
I personally like the answer from Demis Gallisto. If you can work with profiles, this would be my recommendation.
Nowadays people most likely prefer to work with Annotations, so as an addition to his answer.
#Configuration
#Profile({ "test", "prod" })
public class SchedulerConfig {
#Bean
// ... some beans to setup your scheduler
}
This will trigger the scheduler only when the profile test OR prod is active. So if you set an different profile, e.g. -Dspring.profiles.active=dev nothing will happen.
If for some reasons you cannot use the profile approach, e.g. overlap of profiles ...
The solution from miso.belica seems also to work.
Define a property. e.g. in application.properties: dailyRecalculationJob.cron.enabled=false and use it in your SchedulerConfig.
#Configuration
public class SchedulerConfig {
#Value("${dailyRecalculationJob.cron.enabled}")
private boolean dailyRecalculationJobCronEnabled;
#Bean
public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, Trigger trigger) throws
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setAutoStartup(dailyRecalculationJobCronEnabled);
// ...
return factory;
}
// ... the rest of your beans to setup your scheduler
}
I had similar issue: disable scheduler in test scope.
Here is part of my applicationContext.xml
<task:annotation-driven scheduler="myScheduler" />
<task:scheduler id="myScheduler" pool-size="10" />
And I've disabled scheduler using 'primary' attribute and Mockito. Here is my applicationContext-test.xml
<bean id="myScheduler" class="org.mockito.Mockito" factory-method="mock" primary="true">
<constructor-arg value="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler"/>
</bean>
Hope this help!
The simplest way I've found in a spring boot context for tests is to simply:
#MockBean
Scheduler scheduler;
This scala code works:
#Bean
def schedulerFactoryBean(): SchedulerFactoryBean = {
new SchedulerFactoryBean {
override def afterPropertiesSet(): Unit = {}
}
}

Does order matter while injecting properties in ProxyFactoryBean

I am trying to inject the aspects in a service. For this service I am creating a proxied object using classic way.
I have written a bean- baseProxy of type (ProxyFactoryBean) which contains a list of all the required advices.
<bean id="baseProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames">
<list>
<value>methodInvocationAdvice</value>
</list>
</property>
</bean>
I am creating a proxy for the service like this :
<bean id="singproxy" parent="baseProxy">
<property name="target" ref="singtarget" />
<property name="targetClass" value="com.spring.learning.SingingService"></property>
</bean>
Which doesn't work but when I revert these two properties and write like this :
<bean id="singproxy" parent="baseProxy">
<property name="targetClass" value="com.spring.learning.SingingService"></property>
<property name="target" ref="singtarget" />
</bean>
To my surprise it works fine. In spring does it matter on the order for bean ? Or its a special case with ProxyFactoryBean?
I tried with Spring 3.0 I am not sure same behavior exists with previous versions.
Concerning target and targetClass, It's one or the other, but not both. Here's the relevant source (from org.springframework.aop.framework.AdvisedSupport), a parent class of ProxyFactoryBean:
public void setTarget(Object target) {
setTargetSource(new SingletonTargetSource(target));
}
public void setTargetSource(TargetSource targetSource) {
this.targetSource = (targetSource != null ? targetSource : EMPTY_TARGET_SOURCE);
}
public void setTargetClass(Class targetClass) {
this.targetSource = EmptyTargetSource.forClass(targetClass);
}
As you can see, both setTarget() and setTargetClass() write to the same field, so the last assignment wins.

Spring-Version: 2.0.7 integrated with quartz?

i'm using Spring-Version: 2.0.7, do i need to download quartz libraries and their dependencies to use it ? cause at first i though it was needed but its giving me an java.lang.IncompatibleClassChangeError.
So i figured that maybe it was integrated in the spring.jar since according to the 2.5 spring the bean is invoked on the application context trhough the spring library.
How ever when i removed quarta.jar, i can´t acces the JobExecutionContext class. Here is my bean declaration:
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="com.bamboo.common.util.CheckAndProcessFilesJob" />
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="5" />
</map>
</property>
</bean>
The java code
package com.bamboo.common.util;
import org.springframework.scheduling.quartz.QuartzJobBean;
/**
* Created by IntelliJ IDEA.
* User: ernestobuttolazio
* Date: 19-may-2011
* Time: 16:44:54
* To change this template use File | Settings | File Templates.
*/
public class CheckAndProcessFilesJob extends QuartzJobBean {
private int timeout;
private int contador;
/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailBean (5)
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
// do the actual work
contador += timeout;
}
}
You need to use quartz 2.1.6. JobDetailFactoryBean should be used in place of JobDetailBean.The configuration will look like,
<bean name="sampleJobDetail"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass"
value="com.altisource.scheduler.jobs.SampleJob" />
<property name="jobDataAsMap">
<map>
</map>
</property>
</bean>
<bean id="sampleTrigger"
class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="jobDetail" ref="sampleJobDetail" />
<property name="startDelay" value="30000" />
<property name="repeatInterval" value="30000" />
<property name="group" value="YOURCHOICE" />
</bean>
If your are using quartz with clustering, the scheduler factory org.springframework.scheduling.quartz.SchedulerFactoryBean will take only property triggers and restrict providing jobDetails. Make sure you configure scheduler factory org.springframework.scheduling.quartz.SchedulerFactoryBean with only triggers.
You need to use Quartz 1.8.x or earlier with Spring's current wrappers. Quartz 2.0 does not work with them.
You can use Quartz 2.0 along side Spring no problem - just not with Spring's wrappers.
Quartz 2.x versions are not compatible with spring. Some classes of the earlier versions have been converted to interfaces in quartz 2.x. But still it can be used with spring, without the help of it's integration support.
Check out this link http://shyarmal.blogspot.com/2011/07/quartz-20-schedule-cron-with-spring.html.

Hot to run a class method in spring as a thread when the app loads?

i need to run a class from quartz-schedualer, and i need it to be running always and parallel form the main app. The class will always be cheking for new files in a folder to process. I though to include it as a listener in the web.xml, how ever this way the constructor does not run, only the calss is loaded. Any sugestions ?
Here what i added in the web.xml:
<listener>
<listener-class>com.bamboo.common.util.QuartzSchedualer</listener-class>
</listener>
This is how i declared the class:
public class QuartzSchedualer {
public void QuartzSchedualer (){
try{
// Grab the Scheduler instance from the Factory
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// and start it off
scheduler.start();
scheduler.shutdown();
} catch (SchedulerException se) {
se.printStackTrace();
}
}
}
Thank you in advance!
You don't need to include it in web.xml, just load your appcontext in your web.xml as you already do probably, and deal with the scheduling within spring:
The job referring to your business object which has the method to be invoked:
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject" />
<property name="targetMethod" value="doIt" />
<property name="concurrent" value="false" />
</bean>
The trigger that takes care of firing the method:
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="exampleJob" />
<!-- run every morning at 6 AM -->
<property name="cronExpression" value="0 0 6 * * ?" />
</bean>
The schedulerFactoryBean for wiring the trigger:
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger" />
</list>
</property>
</bean>
See further in Spring documentation for 2.5, here for 3.0.

Resources