/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.pollconsumer.quartz2;
import java.util.TimeZone;
import org.apache.camel.CamelContext;
import org.apache.camel.Consumer;
import org.apache.camel.NonManagedService;
import org.apache.camel.Route;
import org.apache.camel.component.quartz2.QuartzComponent;
import org.apache.camel.component.quartz2.QuartzConstants;
import org.apache.camel.component.quartz2.QuartzHelper;
import org.apache.camel.spi.ScheduledPollConsumerScheduler;
import org.apache.camel.support.ServiceSupport;
import org.apache.camel.util.ObjectHelper;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A quartz based {@link ScheduledPollConsumerScheduler} which uses a
* {@link CronTrigger} to define when the poll should be triggered.
*/
public class QuartzScheduledPollConsumerScheduler extends ServiceSupport implements ScheduledPollConsumerScheduler, NonManagedService {
private static final Logger LOG = LoggerFactory.getLogger(QuartzScheduledPollConsumerScheduler.class);
private Scheduler quartzScheduler;
private CamelContext camelContext;
private String routeId;
private Consumer consumer;
private Runnable runnable;
private String cron;
private String triggerId;
private String triggerGroup = "QuartzScheduledPollConsumerScheduler";
private TimeZone timeZone = TimeZone.getDefault();
private volatile CronTrigger trigger;
private volatile JobDetail job;
@Override
public void onInit(Consumer consumer) {
this.consumer = consumer;
// find the route of the consumer
for (Route route : consumer.getEndpoint().getCamelContext().getRoutes()) {
if (route.getConsumer() == consumer) {
this.routeId = route.getId();
break;
}
}
}
@Override
public void scheduleTask(Runnable runnable) {
this.runnable = runnable;
}
@Override
public void unscheduleTask() {
if (trigger != null) {
LOG.debug("Unscheduling trigger: {}", trigger.getKey());
try {
quartzScheduler.unscheduleJob(trigger.getKey());
} catch (SchedulerException e) {
throw ObjectHelper.wrapRuntimeCamelException(e);
}
}
}
@Override
public void startScheduler() {
// the quartz component starts the scheduler
}
@Override
public boolean isSchedulerStarted() {
try {
return quartzScheduler != null && quartzScheduler.isStarted();
} catch (SchedulerException e) {
return false;
}
}
@Override
public void setCamelContext(CamelContext camelContext) {
this.camelContext = camelContext;
}
@Override
public CamelContext getCamelContext() {
return camelContext;
}
public Scheduler getQuartzScheduler() {
return quartzScheduler;
}
public void setQuartzScheduler(Scheduler scheduler) {
this.quartzScheduler = scheduler;
}
public String getCron() {
return cron;
}
public void setCron(String cron) {
this.cron = cron;
}
public TimeZone getTimeZone() {
return timeZone;
}
public void setTimeZone(TimeZone timeZone) {
this.timeZone = timeZone;
}
public String getTriggerId() {
return triggerId;
}
public void setTriggerId(String triggerId) {
this.triggerId = triggerId;
}
public String getTriggerGroup() {
return triggerGroup;
}
public void setTriggerGroup(String triggerGroup) {
this.triggerGroup = triggerGroup;
}
@Override
protected void doStart() throws Exception {
ObjectHelper.notEmpty(cron, "cron", this);
if (quartzScheduler == null) {
// get the scheduler form the quartz component
QuartzComponent quartz = getCamelContext().getComponent("quartz2", QuartzComponent.class);
setQuartzScheduler(quartz.getScheduler());
}
String id = triggerId;
if (id == null) {
id = "trigger-" + getCamelContext().getUuidGenerator().generateUuid();
}
CronTrigger existingTrigger = null;
TriggerKey triggerKey = null;
if (triggerId != null && triggerGroup != null) {
triggerKey = new TriggerKey(triggerId, triggerGroup);
existingTrigger = (CronTrigger)quartzScheduler.getTrigger(triggerKey);
}
// Is an trigger already exist for this triggerId ?
if (existingTrigger == null) {
JobDataMap map = new JobDataMap();
// do not store task as its not serializable, if we have route id
if (routeId != null) {
map.put("routeId", routeId);
} else {
map.put("task", runnable);
}
map.put(QuartzConstants.QUARTZ_TRIGGER_TYPE, "cron");
map.put(QuartzConstants.QUARTZ_TRIGGER_CRON_EXPRESSION, getCron());
map.put(QuartzConstants.QUARTZ_TRIGGER_CRON_TIMEZONE, getTimeZone().getID());
job = JobBuilder.newJob(QuartzScheduledPollConsumerJob.class).usingJobData(map).build();
// store additional information on job such as camel context etc
QuartzHelper.updateJobDataMap(getCamelContext(), job, null);
trigger = TriggerBuilder.newTrigger().withIdentity(id, triggerGroup).withSchedule(CronScheduleBuilder.cronSchedule(getCron()).inTimeZone(getTimeZone())).build();
LOG.debug("Scheduling job: {} with trigger: {}", job, trigger.getKey());
quartzScheduler.scheduleJob(job, trigger);
} else {
checkTriggerIsNonConflicting(existingTrigger);
LOG.debug("Trigger with key {} is already present in scheduler. Only updating it.", triggerKey);
job = quartzScheduler.getJobDetail(existingTrigger.getJobKey());
JobDataMap jobData = job.getJobDataMap();
jobData.put(QuartzConstants.QUARTZ_TRIGGER_CRON_EXPRESSION, getCron());
jobData.put(QuartzConstants.QUARTZ_TRIGGER_CRON_TIMEZONE, getTimeZone().getID());
QuartzHelper.updateJobDataMap(getCamelContext(), job, null);
LOG.debug("Updated jobData map to {}", jobData);
// Ensure the cron schedule is updated
CronTrigger newTrigger = existingTrigger.getTriggerBuilder().withSchedule(CronScheduleBuilder.cronSchedule(getCron()).inTimeZone(getTimeZone())).build();
quartzScheduler.rescheduleJob(triggerKey, newTrigger);
}
}
@Override
protected void doStop() throws Exception {
if (trigger != null) {
LOG.debug("Unscheduling trigger: {}", trigger.getKey());
quartzScheduler.unscheduleJob(trigger.getKey());
}
}
@Override
protected void doShutdown() throws Exception {
}
private void checkTriggerIsNonConflicting(Trigger trigger) {
JobDataMap jobDataMap = trigger.getJobDataMap();
String routeIdFromTrigger = jobDataMap.getString("routeId");
if (routeIdFromTrigger != null && !routeIdFromTrigger.equals(routeId)) {
throw new IllegalArgumentException("Trigger key " + trigger.getKey() + " is already used by route: " + routeIdFromTrigger + ". Cannot re-use it for another route: " + routeId);
}
}
}