In new versions of JBoss (such the 4.0.5 which I am using) there is a quartz-ra.sar service which should allow me to define some a job with a cron like specification.

Searching the forums and the jboss site I was able to put together quickly an example:

import org.jboss.annotation.ejb.ResourceAdapter;import org.jboss.logging.Logger;import org.quartz.Job;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;

import javax.ejb.MessageDriven;import javax.ejb.ActivationConfigProperty;

@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "cronTrigger", propertyValue = "0/2 * * * * ?") })

@ResourceAdapter("quartz-ra.rar")

public class Test implements Job {    private static final Logger log = Logger.getLogger(Test.class);

    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {        log.info("executing Test job");    }}

I packed this into a jar and deployed to jboss and was able to see the “executing Test job” message each 2 seconds.

Next step was to externalize the cronTrigger property to an external file such that the property is not hardcodded in the configuration file. This proved to be a very complicated issue since I could find absolutely no example. After 3 hours of searching I was able to put together a working example. Here are the files:

The java file:

import javax.ejb.EJBException;import javax.ejb.MessageDrivenBean;import javax.ejb.MessageDrivenContext;import javax.jms.Message;import javax.jms.MessageListener;

import org.jboss.logging.Logger;import org.quartz.Job;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;

/*MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "cronTrigger", propertyValue = "0/2 * * * * ?") }) ResourceAdapter("quartz-ra.rar")*/public class Test implements Job, MessageDrivenBean, MessageListener {	private static final Logger log = Logger.getLogger(Test.class);

	public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {		log.info("executing Test job");	}

	/**	 * @see javax.ejb.MessageDrivenBean#ejbRemove()	 * @throws EJBException	 */	public void ejbRemove() throws EJBException {	}

	/**	 * @see javax.ejb.MessageDrivenBean#setMessageDrivenContext(javax.ejb.MessageDrivenContext)	 * @param context	 * @throws EJBException	 */	public void setMessageDrivenContext(MessageDrivenContext context) throws EJBException {	}

	/**	 * @see javax.jms.MessageListener#onMessage(javax.jms.Message)	 * @param message	 */	public void onMessage(Message message) {	}

	public void ejbCreate() {		log.info("Test job created");	}}

The ejb-jar.xml file:

<?xml version="1.0" encoding="UTF-8"?><ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee         http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd" version="2.1">    <enterprise-beans>        <message-driven>            <ejb-name>Test</ejb-name>            <ejb-class>Test</ejb-class>            <messaging-type>org.quartz.Job</messaging-type>            <transaction-type>Container</transaction-type>            <activation-config>                <activation-config-property>                    <activation-config-property-name>cronTrigger</activation-config-property-name>                    <activation-config-property-value>                        <![CDATA[0/2 * * * * ?]]>                    </activation-config-property-value>                </activation-config-property>            </activation-config>        </message-driven>    </enterprise-beans></ejb-jar>

The jboss.xml file:

<?xml version="1.0" encoding="UTF-8"?>

<jboss>   <enterprise-beans>      <message-driven>         <ejb-name>Test</ejb-name>         <destination-jndi-name>dummy</destination-jndi-name>         <resource-adapter-name>quartz-ra.rar</resource-adapter-name>      </message-driven>   </enterprise-beans></jboss>

A short list of links which proved usefull:

Comments:

Xavier Dury -

in ScheduledJob.java

@MessageDriven(messageListenerInterface = Job.class)
public class ScheduledJob implements Job {

public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println(“Beep " + new Date());
}
}

in META-INF/ejb.jar.xml

ScheduledJob cronTrigger 0/5 \* \* \* \* ?

in META-INF/jboss.xml

ScheduledJob quartz-ra.rar ___ #### Len -

might not be a bad idea, if I will need this again, I will surely try.


Richard Kennard -

This blog is quite brilliant. It is exactly what I needed. Thank you so much for taking the time to write it.


geehaa -

Hmm… to store the properties, why not make this into an service MBean? that way jboss could store the cron expression, and you could set it via the JMX console.


Cold-Gin -

In a follow up to my response to Paul: The config file approach works *unless* you are referring to changing the cron expression that is embedded inside of the annotation. If you are looking to externalize the cron expression here:

@ActivationConfigProperty(propertyName = “cronTrigger”, propertyValue = “0/2 * * * * ?")

then the only approach that I can offer is *not* using the annotation. -CG


Len -

thank you for sharing this info


Paul Trickey -

Thanks for the examples Len, they were most useful. The only problem I have is that I want to move the cron string outside the .ear into a separate .properties file, and it would seem that this is not possible..? I have a workaround if anyone’s interested: on the first execute call a reschedule method looks up a cron string in a properties file, reschedules the job and sets a flag. This works fine although I think it’s inelegant.


Len -

Glad I could help, Paul.


Cold-Gin -

I just saw Paul’s post - i.e) if the cron settings could be placed in an external properties file. In the example that I posted, the cron expression can be changed external to your application in the “your-quartz-jobs.xml” file that you reference in the quartz.properties file that I mentioned. The only thing to be aware of is that you must redeploy the application (or touch the .ear file in Unix) after you modify the cron expression, but that shouldn’t be a problem at all if you are running it on a schedule. It has worked great for me so far.


Cold-Gin -

This should be helpful. In order to separate your Quartz configuration from your application in JBoss, do the following. *PLEASE NOTE* I have REMOVED the annotations described above for the following approach.

In the class path of your web application, add a quartz.properties file as shown below (Make sure quartz.properties winds up in your WEB-INF/classes folder). Note the org.quartz.plugin.jobInitializer.fileName setting. This points your app to a quartz xml file that you will place in the JBoss server’s /conf directory. By default, the server looks for quartz_jobs.xml. *Name your file something else*.

quartz.properties:

#===============================================================
# Configure Main Scheduler Properties
#===============================================================

org.quartz.scheduler.instanceName = QuartzScheduler
org.quartz.scheduler.instanceId = AUTO

#===============================================================
# Configure ThreadPool
#===============================================================

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 5
org.quartz.threadPool.threadPriority = 5

#===============================================================
# Configure JobStore
#===============================================================

org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin
org.quartz.plugin.jobInitializer.fileName = your-quartz-jobs.xml
org.quartz.plugin.jobInitializer.overWriteExistingJobs = false
org.quartz.plugin.jobInitializer.failOnFileNotFound = true

Next, place quartz.jar in the WEB-INF/lib folder. Now, in your web.xml file, place the following. This servlet kicks off the scheduler, and is part of the quartz distribution:

QuartzInitializer org.quartz.ee.servlet.QuartzInitializerServlet 1

Finally, here is what your-quartz-jobs.xml should look like:

YourJobName your-group-name Your description test.YourClassName yourKeyName true YourTriggerName your-trigger-group YourJobName your-group-name 0 0/5 \* ? \* MON-THU

where test.YourClassName is a wrapper class for your code that implements the quartz StatefulJob interface (I believe there is another one that you can implement as an alternative). You simply implement the execute() method, and plug your code in there.

Note:

If you change the cron expression in the XML file, you can do a touch (for unix), and the changes will take effect. Otherwise, they will not.


Gavin -

Thanks for your post. I’ve successfully implemented some quartz jobs on a single machine environment. What approach would you recommend be for setting up quartz jobs in a clustered environment, where the requirement is for a job to be executed only once per environment in a given interval?


Cold-Gin -

I was able to get Hibernate and Quartz working inside of JBoss. I set up a c3p0 connection pool in JBoss (created a datasource service xml file and placed it in the deploy directory), and then changed the hibernate config file of my application to point at the JNDI name for the pool instead of configuring a JDBC connection). I am also using a factory with a Singleton pattern to create the encapsulated SessionFactory one time. This is important so that you don’t have to change your session creation code all over the place.

I made no changes at all to the existing code. I just had to set up the pool and change my hibernate config file (again, your situation may be different if you are not using a factory to manage the creation of your sessions). Then, using the example above, I created my class inside of the execute() method and then execute() delegates calls to my class.

Thanks a lot for this helpful site!


Homer -

I was getting the same error with the dummy line in there. While trying to debug my transaction issues I was also see that for some reason in my “example 2” bean the setMessageDrivenContext method was never being called. It turned out that for some reason having that “example 1” class still in the jar the “example 2” (of course I had different class names for each version - not “Test”) was not behaving properly.

I removed the “example 1” class and then the dummy line in jboss.xml was no longer a problem. Also, with that class removed the setMessageDrivenContext is now being called. At this point we are seeing more predictable with transactions (though not claiming victory yet).

Does anyone have an explaination as to why having both example 1 and 2 in the same jar would cause these problems?


homer -

Thanks a bunch for the samples!

Has anyone had any luck creating a Quartz job that uses Hibernate to update a db and within the same transaction add a message to a JMS Queue? I can get this type of a transaction to work fine from a servlet but I can’t seem to get JMS to participate in the trx within a Quartz job.


jfrankman -

I got the second example to work, but I had to remove the following from the jboss.xml:

dummy

Don’t know why this fixed it, but if I kept this in the jboss.xml I got the error listed in my previous post.


rkeller -

I’m running into the same problem trying to use a message bean with quartz on JBoss 4.0.5 and Hibernate persistence. I can’t seem to get it to work either. Any updates on your issue? Did you find any help?


jfrankman -

I have tried everything I could think of. No matter what, the above configuration will only assume the classpath of JBoss. I got desparate and unzipped the quartz-ra.rar and added the persistence.xml file to it I then restarted JBoss. Finally, it looks like it found the persistence.xml file, but now its having other problem with second leve cach and the like. I am still facing problems because the quartz scheduled job seems to running with its own default configuration instead fo the deployed EAR.

org.quartz.SchedulerException: Job threw an unhandled exception. [See nested exception: javax.ejb.EJBTransactionRolledbackException: javax.persistence.PersistenceException: org.hibernate.cache.NoCachingEnabledException: Second-level cache is not enabled for usage [hibernate.cache.use_second_level_cache | hibernate.cache.use_query_cache]]
at org.quartz.core.JobRunShell.run(JobRunShell.java:214)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:520)
* Nested Exception (Underlying Cause) —————
javax.ejb.EJBTransactionRolledbackException: javax.persistence.PersistenceException: org.hibernate.cache.NoCachingEnabledException: Second-level cache is not enabled for usage [hibernate.cache.use_second_level_cache | hibernate.cache.use_query_cache]
at org.jboss.ejb3.tx.Ejb3TxPolicy.handleInCallerTx(Ejb3TxPolicy.java:93)
at org.jboss.aspects.tx.TxPolicy.invokeInCallerTx(TxPolicy.java:130)
at org.jboss.aspects.tx.TxInterceptor$Required.invoke(TxInterceptor.java:195)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
at org.jboss.ejb3.stateless.StatelessInstanceInterceptor.invoke(StatelessInstanceInterceptor.java:62)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
at org.jboss.ejb3.mdb.MessagingContainer.localInvoke(MessagingContainer.java:245)
at org.jboss.ejb3.mdb.inflow.MessageInflowLocalProxy.delivery(MessageInflowLocalProxy.java:268)
at org.jboss.ejb3.mdb.inflow.MessageInflowLocalProxy.invoke(MessageInflowLocalProxy.java:138)
at $Proxy136.execute(Unknown Source)
at org.jboss.resource.adapter.quartz.inflow.QuartzJob.execute(QuartzJob.java:57)
at org.quartz.core.JobRunShell.run(JobRunShell.java:203)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:520)
Caused by: javax.persistence.PersistenceException: org.hibernate.cache.NoCachingEnabledException: Second-level cache is not enabled for usage [hibernate.cache.use_second_level_cache | hibernate.cache.use_query_cache]
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:698)
at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:121)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:51)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:33)
at com.idfbins.nexus.batch.GISDataBatch.init(GISDataBatch.java:46)
at com.idfbins.nexus.batch.GISDataBatch.assignLocationsCoordinates(GISDataBatch.java:59)
at com.idfbins.nexus.batch.ExecuteGISBatch.execute(ExecuteGISBatch.java:42)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:112)
at org.jboss.ejb3.interceptor.InvocationContextImpl.proceed(InvocationContextImpl.java:166)
at org.jboss.ejb3.interceptor.EJB3InterceptorsInterceptor.invoke(EJB3InterceptorsInterceptor.java:63)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
at org.jboss.ejb3.entity.TransactionScopedEntityManagerInterceptor.invoke(TransactionScopedEntityManagerInterceptor.java:54)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
at org.jboss.ejb3.AllowedOperationsInterceptor.invoke(AllowedOperationsInterceptor.java:46)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
at org.jboss.aspects.tx.TxPolicy.invokeInCallerTx(TxPolicy.java:126)
… 11 more
Caused by: org.hibernate.cache.NoCachingEnabledException: Second-level cache is not enabled for usage [hibernate.cache.use_second_level_cache | hibernate.cache.use_query_cache]
at org.hibernate.cache.NoCacheProvider.buildCache(NoCacheProvider.java:21)
at org.hibernate.cache.UpdateTimestampsCache.(UpdateTimestampsCache.java:42)
at org.hibernate.impl.SessionFactoryImpl.(SessionFactoryImpl.java:337)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1218)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:691)
… 30 more


Marilen Corciovei -

There is a org/quartz/quartz.properties in jboss-4.0.5.GA/server/default/lib/quartz-all-1.5.2.jar but I don’t know it’s purpose and I did not touched it. I doubt that you have to change there.


jfrankman -

Thank for your help. I am having one more issue. My job uses java persistence and I get the following error: “Could not find any META-INF/persistence.xml file in the classpath”

I beleive that this is due to some sort of class path or class loader issue. I have read somewhere that you can set the useClassLoaderOfInitializer property to true, but I cannot figure out where to set it. I have looked for a quartz.properties file but never found one. Where can this and other properties get set?


Marilen Corciovei -

should not be something else. I’m using jboss 4.0.5GA with EJB3 with the standard example modified as described. But with JBoss you can expect anything.


Marilen Corciovei -

12:42:27,458 WARN [verifier] EJB spec violation:
Bean : Test
Section: 15.7.2
Warning: A message driven bean must implement, directly or indirectly, the javax.ejb.MessageDrivenBean interface.

12:42:27,458 WARN [verifier] EJB spec violation:
Bean : Test
Section: 15.7.2
Warning: A message driven bean must implement, directly or indirectly, the message listener class

12:42:27,460 WARN [verifier] EJB spec violation:
Bean : Test
Section: 15.7.3
Warning: The message driven bean must declare one ejbCreate() method.

12:42:27,461 WARN [verifier] EJB spec violation:
Bean : Test
Section: 15.7.4
Warning: The message driven bean must declare one onMessage() method.

12:42:27,461 WARN [verifier] EJB spec violation:
Bean : Test
Section: 15.7.5
Warning: The message driven bean must declare one ejbRemove() method.


littleram -

Dude, I’ve been looking for something like this all over the web. Thanks a bunch!


James Frankman -

The first example worked, but the second one I get this error:

ObjectName: jboss.j2ee:ear=nexus-poc-2.ear,jar=nexus-poc-2.jar,name=QuartzTst2,service=EJB3
State: FAILED
Reason: org.jboss.deployment.DeploymentException: Error for ActivationSpec class org.jboss.resource.adapter.quartz.inflow.QuartzActivationSpec as JavaBean; - nested throwable: (java.beans.IntrospectionException: No property found for: destination on JavaBean: jobName=job.0.1186097660143,jobGroup=default,triggerName=trigger.1.1186097660143,triggerGroup=default,cronTrigger=nullvolatilityfalsedurabilityfalserecoverablefalse)

Is the second example deployed to JBoss with ejb3 enabled or a standard j2ee configuration?


Marilen Corciovei -

EJB3 enabled.


James Frankman -

Still can’t get the second example to work. Is there anything else I need to do to make this work? This is my first time using JMS so maybe there is some extra JBoss configuration needed to get this to work? How does the testBean know about the destination “dummy”. Shouldn’t there be something in the ejb-.xml or the Test class itself that should reference ‘dummy’?


James Frankman -

Is there anything else in terms of JMS configuration that I need to do to make this work? I am new to MDBs so is there something that has to be done in terms of configuration?


MC -

I have the following scenario: quartz scheduler calls>java class calls >shell script > calls java/hibernate job Quartz is running in tomcat thru the QuartzInitliazerServlet. The hibernate application is outside of tomcat. When Quartz starts the job, the trigger jams and goes into “blocked” or “misfire” and the job hangs. If I restart tomcat the job will resume and finish its work, not sure if any db inserts/updates are missed. Why can’t i run my hibernate job successfully? any ideas?


len -

Check for classloading issues.