Sunday, September 13, 2009

Quartz persistence and Spring

I need run some application on my home machine after 4pm every work day. My machine is not always on. If the machine is on before 4pm, the appliation should wait until 4pm. If the machine is on after 4pm, the appliction should be triggered right away. Quartz can schedule job like Cron. I started from there. I use the wrapper from Spring framework.

My setting about cron is this

0 0 16 ? * MON-FRI


Then I found the job cannot be triggered if the application was started after 4pm. I set misfireInstructionName to be MISFIRE_INSTRUCTION_FIRE_ONCE_NOW. I hope the crontrigger could find there is a “misfire” and fire it at once but it did not.



MISFIRE need “job persistence”. That make sense, if the job was not persistent, how can the job in a brand new JVM know that one job is misfired. Job persistence is also import to support quartz jobs in cluster environment.



It is not difficult to make the job persistence after following the quartz docs. But new problem occured. The exception about “Job cannot be serializable ” was thrown out. The reason is the jobDetail depends on some services from spring context. The jobDetail will be serialzed and saved in database (Blob). But the service from spring is not serialziable.



To solve it, the Job Detail class, you need to create a property for the service(with a public setter). Then you put, the reference to the service bean, not in your job data map, but in the scheduler context (property on the scheduler factory bean). Then it works fine.



<bean class="org.springframework.scheduling.quartz.JobDetailBean" id="dataCollectJobDetail">

        <property value="my.stock.quartz.DataCollectJob" name="jobClass"></property>


    </bean>



    <bean class="org.springframework.scheduling.quartz.CronTriggerBean" id="cronTrigger">

        <property name="jobDetail" ref="dataCollectJobDetail"></property>


        <property value="0 0 16 ? * MON-FRI" name="cronExpression"></property>


        <property value="MISFIRE_INSTRUCTION_FIRE_ONCE_NOW" name="misfireInstructionName"></property>


    </bean>



<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

        <property name="triggers">


            <list>


                <ref bean="cronTrigger" />


            </list>


        </property>


        <property name="schedulerContextAsMap">


            <map>


                <entry key="dataCollectTaskManager">


                    <ref bean="dataCollectTaskManager" />


                </entry>


            </map>


        </property>


        <property name="quartzProperties">


            <props>


                <prop key="org.quartz.jobStore.class">


                    org.quartz.impl.jdbcjobstore.JobStoreTX


                </prop>


                <prop key="org.quartz.jobStore.driverDelegateClass">


                    org.quartz.impl.jdbcjobstore.StdJDBCDelegate


                </prop>


                <prop key="org.quartz.jobStore.dataSource">


                    myDS


                </prop>


                <prop key="org.quartz.dataSource.myDS.driver">


                    ${jdbc.driverClassName}


                </prop>


                <prop key="org.quartz.dataSource.myDS.URL">


                    ${jdbc.url}


                </prop>


                <prop key="org.quartz.dataSource.myDS.user">


                    ${jdbc.username}


                </prop>


                <prop key="org.quartz.dataSource.myDS.password">


                    ${jdbc.password}


                </prop>


                <prop key="org.quartz.jobStore.tablePrefix">


                    QRTZ_


                </prop>


            </props>


        </property>


    </bean>



 



public class DataCollectJob extends QuartzJobBean {

    private DataCollectTaskManager dataCollectTaskManager;


    public void setDataCollectTaskManager(DataCollectTaskManager dataCollectTaskManager) {


        this.dataCollectTaskManager = dataCollectTaskManager;


    }



    protected void executeInternal(JobExecutionContext jobContext)

        throws JobExecutionException {


        System.out.println("Previous Fire Time: " + jobContext.getPreviousFireTime());


        System.out.println("Current Fire Time: " + jobContext.getFireTime());


        System.out.println("Next Fire Time: " + jobContext.getNextFireTime());


        try {


            dataCollectTaskManager.collectStock();


        } catch (InterruptedException e) {


            // TODO Auto-generated catch block


            e.printStackTrace();


        }


    }




The job still cannot be triggered at the first time. Nothing in database before that. But after the first running, the job is persisted in tables and the next fired time was calcualted and saved. When the job is started the next day, even it is after 4pm, it can find there is a “misfire” and trigger the job right away. It is fine to me.

No comments:

Post a Comment