I have a web app that has to do something every, let's say first day of every month.
It's a GWT application divided into 4 projects (if that ever matters) and I added these jars using Maven (which updated my pom.xml):
opensymphony quartz 1.6.3
commons-collections
Since I am already using Spring, I followed this tutorial (Tutorial in French)
and added what's written in the tutorial in my application-context.xml file.
At compile time, no problem, but at runtime, I have this error :
com.google.gwt.user.client.rpc.StatusCodeException: Error 500 Error creating bean with name 'schedulerFactory' defined in class path resource [application-context.xml]: Cannot resolve reference to bean 'cronTrigger' while setting bean property 'triggers' with key [0];nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cronTrigger' defined in class path resource [application-context.xml]: Error setting property values;nested exception is org.springframework.beans.PropertyBatchUpdateException;nested PropertyAccessExceptions (1) are: PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'cronExpression' threw exception;nested exception is java.text.ParseException: Unexpected end of expression.
Where does it come from ?
A part of my application-context.xml :
<!-- Configuration du crontrigger -->
<bean id="schedulerFactory" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger" />
</list>
</property>
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<ref local="exampleJob" />
</property>
<!-- run every day at 6AM -->
<property name="cronExpression" value="0 0 6 * * ?" />
</bean>
<bean id="exampleJob" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="fr.web.utils.ExampleJob" />
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="5" />
</map>
</property>
</bean>
Issue is you have given reference in Scheduler's trigger cronTrigger which you haven't declare in the XML file.
Provide XML for more detailed answer
Update
Your cronExpression isn't seems to be valid
0 0 6 * * ? make it 0 0 6 * * ? note the last space before ?
Related
It seems that the built in workflow activities are being executed twice. I am testing the checkout workflow and the DecrementInventoryActivity is removing the quantity from the sku twice.
Is this a known bug or am I doing something wrong?
I created the workflow like so:
<!-- Checkout Workflow Configuration -->
<bean id="blCheckoutWorkflow" class="org.broadleafcommerce.core.workflow.SequenceProcessor">
<property name="processContextFactory">
<bean class="org.broadleafcommerce.core.checkout.service.workflow.CheckoutProcessContextFactory"/>
</property>
<property name="activities">
<list>
<bean p:order="6000" id="blDecrementInventoryActivity" class="org.broadleafcommerce.core.checkout.service.workflow.DecrementInventoryActivity">
<property name="rollbackHandler" ref="blDecrementInventoryRollbackHandler" />
</bean>
<bean p:order="7000" id="blCompleteOrderActivity" class="org.broadleafcommerce.core.checkout.service.workflow.CompleteOrderActivity">
<property name="rollbackHandler" ref="blCompleteOrderRollbackHandler" />
</bean>
<bean p:order="9999999" class="com.mycompany.workflow.checkout.NotifyExternalInventorySystem" />
</list>
</property>
<property name="defaultErrorHandler">
<bean class="org.broadleafcommerce.core.workflow.DefaultErrorHandler">
<property name="unloggedExceptionClasses">
<list>
<value>org.broadleafcommerce.core.inventory.service.InventoryUnavailableException</value>
</list>
</property>
</bean>
</property>
</bean>
Starting with Broadleaf 4.0, the DecrementInventoryActivity was added by default to the blCheckoutWorkflow. See the 3.1.10-4.0.0 migration notes at http://www.broadleafcommerce.com/docs/core/4.0/migration-notes/3.1-to-4.0-migration/3.1.10-to-4.0-migration, in the section "Inventory Management".
This also goes for the defaultErrorHandler, and you can remove the blCompleteOrderActivity (that has always been managed in the framework). Basically, your customized blCheckoutWorkflow bean should change to:
<bean id="blCheckoutWorkflow" class="org.broadleafcommerce.core.workflow.SequenceProcessor">
<property name="activities">
<list>
<bean p:order="9999999" class="com.mycompany.workflow.checkout.NotifyExternalInventorySystem" />
</list>
</property>
</bean>
Starting with Broadleaf 3.0, any modifications to the blCheckoutWorkflow bean undergo the Broadleaf XML merging processing (which merges bean ids like blCheckoutWorkflow's list of activities). In your case, since the DecrementInventoryActivity is already defined in the core framework XML file and your definition of blCheckoutWorkflow merges with it, the final result is 2 instances of the DecrementInventoryActivity.
As far as I tried, I have to manually change the CRON_TRIGGERS table in DB. Dirty...
Any way to make more like this?:
There are 2 apps running, both have in .properties file schedule defined as "every minute" and so works the job
I stop one instance and reconfigure (change in .properties file), so the schedule is "every hour"
I start the instance. Now I would like that instace to check, that such job is already defined in DB and to update the schedule there. It is not happening now using configuration from site http://www.objectpartners.com/2013/07/09/configuring-quartz-2-with-spring-in-clustered-mode/
Or what is the typical solution?
So I guess that when you say .properties file, you actually mean the spring bean XML file(s).
It does not make any sense that you statically configure identical jobs with different schedules. If for whatever reason, one instance restarts, it will automatically apply its own schedule. If statically configured, your job triggers should be the same on all instances
If you properly set <property name="overwriteExistingJobs" value="true"/> in your SchedulerFactoryBean it should automatically updates the schedule of the job.
You should never modify the database manually. Always update the scheduler through its API.
Try sth like this:
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobDetails">
<list>
<ref bean="yourJobDetail" />
</list>
</property>
<property name="triggers">
<list>
<ref bean="yourJobTrigger" />
</list>
</property>
<property name="configLocation" value="file:${HOME}/yourProperties.properties" />
<!-- Commented, because don't work with autocommit = false on spring data source -->
<!-- <property name="dataSource" ref="mainDataSource"/> -->
<property name="transactionManager" ref="mainTransactionManager" />
<property name="autoStartup" value="true" />
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
<property name="jobFactory">
<bean class="FactoryForJobWithInjectionOfSpringBbean" />
</property>
<!-- Will update database cron triggers to what is in this jobs file on each deploy. Replaces all previous trigger and job data that
was in the database. YMMV -->
<!-- dont work properly with cluster -->
<!-- <property name="overwriteExistingJobs" value="true" /> -->
</bean>
Unfortunately i think that:
<property name="overwriteExistingJobs" value="true" />
dosen't work correctly in cluster mode.
I have some java written to do a very very simple operation. It needs to happen once every three hours and is not connected to any user action, it's just something that revolves every three hours.
For this reason, I'm having trouble troubleshooting. The operation isn't happening. Assuming the java is intact, is there something that is supposed to "start" the cron? Or should I expect it to just be going once the server is restarted?
<bean id="queueJob" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="com.campbiche.pb.service.scheduler.BaseQuartzScheduler" />
<property name="jobDataAsMap">
<map>
<entry key="processorName" value="scheduleListingActions" />
<entry key="methodName" value="revolveQueue" />
</map>
</property>
</bean>
<bean id="queueCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="queueJob" />
<!-- run every 1 miunute -->
<property name="cronExpression" value="*/1 * * * * ?" />
</bean>
Working in SpringSource. The Cron is set to one minute for testing. "" has already been added to a schedulerfactorybean bean as well but I did not include the code here for brevity.
It will be triggered when CronTriggerBean is instantiated.
One more thing I recall, and confirmed after looking in the docs(3.x), that you need to add your queueCronTrigger to SchedulerFactoryBean.
I need findItemByPIdEndDate() method of the MngtImpl class to be invoked every 5000ms, but nothing appears to be happening. Am I missing something?
<bean id="findItemByPIdEndDate" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="MngtImpl"/>
<property name="targetMethod" value="findItemByPIdEndDate"/>
<property name="repeatInterval" value="50000"/>
</bean>
#matt b I've read some of this, everything is new to me here ..so I came with this .. and again its not working, what am I missing this time ?
<bean id="findItemByPIdEndDate" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="MngtImpl" />
<property name="targetMethod" value="findItemByPIdEndDate" />
</bean>
<bean id="compareDateTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<property name="jobDetail" ref="findItemByPIdEndDate" />
<property name="startDelay" value="0" />
<property name="repeatInterval" value="50000" />
</bean>
For this task, the Chapter 23. Scheduling and Thread Pooling is your friend. That said, here is a short summary.
First, define your Job:
<bean id="findItemByPIdEndDate" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="MngtImpl"/>
<property name="targetMethod" value="findItemByPIdEndDate"/>
</bean>
Now, you need to schedule the job using a trigger and a SchedulerFactoryBean. For the trigger, I suggest to use a SimpleTriggerBean in your case:
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<!-- see the example of method invoking job above -->
<property name="jobDetail" ref="findItemByPIdEndDate" />
<!-- 10 seconds -->
<property name="startDelay" value="10000" />
<!-- repeat every 50 seconds -->
<property name="repeatInterval" value="50000" />
</bean>
To finalize everything, set up the SchedulerFactoryBean:
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="simpleTrigger" />
</list>
</property>
</bean>
You need a lot more plumbing than that to make Quartz work. Just declaring the MethodInvokingJobDetailFactoryBean on its own will do nothing.
However, Quartz is overkill for this, Java5+ can do this on its own. I suggest reading up on Spring's ScheduledExecutorFactoryBean, which in combination with MethodInvokingRunnable, allows you to invoke your method periodically.
What you've done so far is the equivalent of only instantiating a MethodInvokingJobDetailFactoryBean() - essentially all you've done is created the Job. Now you need to have some configuration for how it's scheduled, and what triggers it.
Take a look at the section in the Spring manual on Quartz.
In my application there is a requirement to be able to create Scheduled Job(s) depending on the type of Request that comes in (Dynamically).
Can I still use Spring to create and trigger Jobs? If Yes, how?
Any help would be useful.
Given that the SchedulerFactoryBean exposes a native Quartz Scheduler object, you can wire that directly into your controller class, and then dynamically create and register triggers and jobs with the Scheduler object.
Spring itself can't be used for the scheduling of the dynamically created jobs, since Spring's bean support will be used for statically configured jobs, but the native Quartz Scheduler API is reasonable enough to use on its own (barely). As fr triggering of the jobs, that Quartz's job, not Spring's.
edit: either I'm mis-understanding the original question, or everyone else is. The other answers all detail how to statically wire up a series of quartz jobs using Spring, but the question was how to dynamically schedule jobs as requests come in.
Look at CronTriggerBean and JobDetailBean. The 'MyJob' class mocked up below is an instance of QuartzJobBean. The cron expression is what you'd expect, but with seconds as its first value.
<beans>
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="startupDelay" value="5"/>
<property name="waitForJobsToCompleteOnShutdown" value="false"/>
<property name="triggers">
<list>
<bean class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<bean class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="edu.vt.MyJob"/>
<property name="jobDataAsMap">
<map>
<entry key="messageSource" value-ref="messageSource"/>
<entry>
<key><value>anotherProperty</value></key>
<bean class="edu.vt.MyUsefulBean">
<constructor-arg index="0" value="..."/>
</bean>
</entry>
</map>
</property>
</bean>
</property>
<property name="cronExpression" value="0 * * * * ?"/>
</bean>
</list>
</property>
</bean>
</beans>
There does not seem to be much complete information on this. This is how I schedule jobs dynamically. Of course you could replace the simple trigger with some other trigger.
Spring beans:
<bean name="dailyUpdateJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.somecompany.scheduler.DailyUpdates" />
</bean>
<bean id="dailyCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="dailyUpdateJob" />
<!-- run every morning at 4:15 AM -->
<property name="cronExpression" value="00 15 04 * * ?" />
</bean>
<bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="dailyCronTrigger" />
<ref bean="weeklyReportsCronTrigger" />
</list>
</property>
<property name="applicationContextSchedulerContextKey">
<value>applicationContext</value>
</property>
</bean>
To run the job immediately get a reference to the scheduler and the job, attach a simple trigger and put it into the scheduler, like this:
#Autowired
SchedulerFactoryBean scheduler;
#Autowired
#Qualifier("dailyUpdateJob")
JobDetailFactoryBean dailyJob;
public void dynamicJobTrigger() throws Exception {
// Create a trigger for "now"
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.startAt(new Date())
.forJob(dailyJob.getObject())
.build();
// And drop it into the scheduler for immediate execution
scheduler.getScheduler().scheduleJob(trigger);
}
You can download sample source code from this link
<?xml version="1.0" encoding="UTF-8"?>
<!-- scheduler factory -->
<bean id="com.notary.app.invoicing.scheduler.SchedulerFactory"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="ASFImportTrigger"/>
</list>
</property>
<property name="dataSource">
<ref bean="datasource"/>
</property>
<property name="transactionManager">
<ref bean="transactionManager"/>
</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.MSSQLDelegate</prop>
<prop key="org.quartz.jobStore.misfireThreshold">60000</prop>
<prop key="org.quartz.jobStore.selectWithLockSQL">SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?</prop>
<prop key="org.quartz.plugin.triggHistory.class">org.quartz.plugins.history.LoggingTriggerHistoryPlugin</prop>
<prop key="org.quartz.plugin.triggHistory.triggerFiredMessage">Trigger {1}.{0} fired job {6}.{5} at: {4, date, HH:mm:ss dd/MM/yyyy}</prop>
<prop key="org.quartz.plugin.triggHistory.triggerCompleteMessage">Trigger {1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss dd/MM/yyyy} with resulting trigger instruction code: {9}</prop>
<prop key="org.quartz.plugin.jobHistory.class">org.quartz.plugins.history.LoggingJobHistoryPlugin</prop>
<prop key="org.quartz.plugin.jobHistory.jobSuccessMessage">Job {1}.{0} fired at: {2, date, dd/MM/yyyy HH:mm:ss} result=OK</prop>
<prop key="org.quartz.plugin.jobHistory.jobFailedMessage">Job {1}.{0} fired at: {2, date, dd/MM/yyyy HH:mm:ss} result=ERROR</prop>
</props>
</property>
<property name="overwriteExistingJobs" value="true"/>
<property name="startupDelay" value="50"/>
<property name="applicationContextSchedulerContextKey">
<value>applicationContext</value>
</property>
</bean>
You can also get Spring to trigger methods on your beans using Quartz (i.e. youdon't need to create any Quartz-specific classes at all) using the MethodInvokingJobDetailFactoryBean in the package org.springframework.scheduling.quartz
A year later and I find myself having to something very similar. Googling around, I found this link which describes getting access to the application context from within a scheduled job through the JobExecutionContext. I think I will be creating an abstract type job that can do some of the actual job creation and use a prototype to actual inject required services when the job needs to run.
Spring 3 (latest version at time of writing) supports setting up jobs almost completely with annotations.
See: Spring reference on scheduling