I'm hoping one of you lovely people will be able to help me with this, as I've spent a number of fruitless hours already trying to make everything play nice!
I've traced the issue down to Classloading, and been able to see that when Quartz tries to de-serialise jobDetail's from a jobStore (jobStoreCMT), the Classloader used does not contain any of my applications classes, and only the libraries defined in the EARs lib folder.
So... I'm obviously using an application server, and in this case tried against Glassfish 3.1.1/3.1.2
tried against Quartz 1.8.6/2.1.5 using Spring 3.1.0.RELEASE
Spring/Quartz config:
<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="overwriteExistingJobs" value="true" />
<property name="triggers">
<list>
<ref bean="notificationEmailsSimpleTrigger" />
</list>
</property>
<property name="quartzProperties">
<props>
<prop key="org.quartz.scheduler.instanceName">QuartzScheduler</prop>
<prop key="org.quartz.scheduler.instanceId">AUTO</prop>
<prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
<prop key="org.quartz.threadPool.threadCount">25</prop>
<prop key="org.quartz.threadPool.threadPriority">5</prop>
<prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreCMT</prop>
<prop key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.StdJDBCDelegate</prop>
<prop key="org.quartz.jobStore.misfireThreshold">60000</prop>
<prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop>
<!-- <prop key="org.quartz.jobStore.isClustered">true</prop> -->
<!-- <prop key="org.quartz.jobStore.clusterCheckinInterval">20000</prop> -->
<prop key="org.quartz.scheduler.classLoadHelper.class">org.quartz.simpl.CascadingClassLoadHelper</prop>
<prop key="org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer">true</prop>
<prop key="org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread">true</prop>
<prop key="org.quartz.scheduler.skipUpdateCheck">true</prop>
</props>
</property>
</bean>
and the corresponding trigger reference:
<bean id="notificationEmailsSimpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="jobDetail" ref="notificationJobDetail" />
<property name="repeatInterval" value="60000" />
</bean>
<bean id="notificationJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.mcboom.social.notifications.NotificationQuartzJobBean" />
</bean>
So the problem I'm having is this: any combination of the below doesn't seem to effect the classloader being used.
<prop key="org.quartz.scheduler.classLoadHelper.class">org.quartz.simpl.CascadingClassLoadHelper</prop>
<prop key="org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer">true</prop>
<prop key="org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread">true</prop>
Or more specifically dont help when trying to retrieve a previously persisted trigger, resulting in the following stracktrace:
INFO: ERROR - ErrorLogger.schedulerError(schedulerFactoryBean_QuartzSchedulerThread)(2358) | An error occured while scanning for the next trigger to fire.
org.quartz.JobPersistenceException: Couldn't acquire next trigger: Couldn't retrieve trigger: com.mcboom.social.notifications.NotificationQuartzJobBean [See nested exception: org.quartz.JobPersistenceException: Couldn't retrieve trigger: com.mcboom.social.notifications.NotificationQuartzJobBean [See nested exception: java.lang.ClassNotFoundException: com.mcboom.social.notifications.NotificationQuartzJobBean]]
at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTrigger(JobStoreSupport.java:2814)
at org.quartz.impl.jdbcjobstore.JobStoreSupport$36.execute(JobStoreSupport.java:2757)
at org.quartz.impl.jdbcjobstore.JobStoreSupport.executeInNonManagedTXLock(JobStoreSupport.java:3788)
at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTrigger(JobStoreSupport.java:2753)
at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:263)
Caused by: org.quartz.JobPersistenceException: Couldn't retrieve trigger: com.mcboom.social.notifications.NotificationQuartzJobBean [See nested exception: java.lang.ClassNotFoundException: com.mcboom.social.notifications.NotificationQuartzJobBean]
at org.quartz.impl.jdbcjobstore.JobStoreSupport.retrieveTrigger(JobStoreSupport.java:1596)
at org.quartz.impl.jdbcjobstore.JobStoreSupport.retrieveTrigger(JobStoreSupport.java:1572)
at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTrigger(JobStoreSupport.java:2792)
... 4 more
I can see the org.quartz.simpl.CascadingClassLoadHelper being used on-load, and correctly selecting the right classloader.
Problem is that when the QuartzSchedulerThread tries to retrieve a trigger it uses JobStoreSupport.retrieveTrigger(), which in turn falls-back to ObjectInputsStream.resolveClass(), and the following line of code:
Class.forName(name, false, latestUserDefinedLoader())
Where latestUserDefinedLoader() always returns the wrong classloader...resulting in the ClassNotFoundException and leaving me pretty flummoxed!
I should point out that latestUserDefinedLoader() is a native method of ObjectInputsStream, and i'm using jdk 1.6 on OSX.
Can anyone from either the Quartz/ Spring or more likely Glassfish community shed some light on this, I'm pulling my hair out at the moment.
Thanks Steve.