Skip to content

Commit

Permalink
CAMEL-20115: Support for Start Date and End Date in camel-quartz (#12098
Browse files Browse the repository at this point in the history
)

* CAMEL-20115: Support for Start Date and End Date in camel-quartz

* Fix for exact match in CamelJob for fireTime

* Move to Date with Timezone format

* Updated adoc and removed string concatenation from Test

---------

Co-authored-by: Prasanth Rao <[email protected]>
  • Loading branch information
prasanthrao and Prasanth Rao authored Nov 24, 2023
1 parent 77c3b25 commit 2a90c15
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 8 deletions.
21 changes: 21 additions & 0 deletions components/camel-quartz/src/main/docs/quartz-component.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,27 @@ quartz://groupName/timerName?cron=0+0/5+12-18+?+*+MON-FRI&trigger.timeZone=Europ

The timeZone value is the values accepted by `java.util.TimeZone`.

== Specifying start date

The Quartz Scheduler allows you to configure start date per trigger. You can provide the start date
in the date format yyyy-MM-dd'T'HH:mm:ssz.

----
quartz://groupName/timerName?cron=0+0/5+12-18+?+*+MON-FRI&trigger.startAt=2023-11-22T14:32:36UTC
----

== Specifying end date

The Quartz Scheduler allows you to configure end date per trigger. You can provide the end date
in the date format yyyy-MM-dd'T'HH:mm:ssz.

----
quartz://groupName/timerName?cron=0+0/5+12-18+?+*+MON-FRI&trigger.endAt=2023-11-22T14:32:36UTC
----

Note: Start and end dates may be affected by time drifts and unpredictable behavior during
daylight saving time changes. Exercise caution, especially in environments where precise timing is critical.

== Configuring misfire instructions

The quartz scheduler can be configured with a misfire instruction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.apache.camel.component.quartz;

import java.util.Collection;
import java.util.Date;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicReference;

Expand Down Expand Up @@ -53,6 +54,12 @@ public class CamelJob implements Job, InterruptableJob {
public void execute(JobExecutionContext context) throws JobExecutionException {
Exchange exchange = null;
try {
if (hasTriggerExpired(context)) {
LOG.warn("Trigger exists outside StartTime={} and EndTime={}. Skipping CamelJob jobExecutionContext={}",
context.getTrigger().getStartTime(), context.getTrigger().getEndTime(), context);
return;
}

if (LOG.isDebugEnabled()) {
LOG.debug("Running CamelJob jobExecutionContext={}", context);
}
Expand Down Expand Up @@ -94,6 +101,29 @@ public void execute(JobExecutionContext context) throws JobExecutionException {
}
}

/**
* Validates if the Fire Time lies within the Start Time and End Time
*
* @param context
*
* @return
*/
private boolean hasTriggerExpired(JobExecutionContext context) {
Date fireTime = context.getFireTime();

// Trigger valid if Start Time is null or before Fire Time
Date startTime = context.getTrigger().getStartTime();
boolean validStartTime
= context.getTrigger().getStartTime() == null || fireTime.equals(startTime) || fireTime.after(startTime);

// Trigger valid if End Time is null or after Fire Time
Date endTime = context.getTrigger().getEndTime();
boolean validEndTime
= context.getTrigger().getEndTime() == null || fireTime.equals(endTime) || fireTime.before(endTime);

return !(validStartTime && validEndTime);
}

protected CamelContext getCamelContext(JobExecutionContext context) throws JobExecutionException {
SchedulerContext schedulerContext = getSchedulerContext(context);
String camelContextName = context.getMergedJobDataMap().getString(QuartzConstants.QUARTZ_CAMEL_CONTEXT_NAME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package org.apache.camel.component.quartz;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
Expand Down Expand Up @@ -382,14 +383,7 @@ private void addJobInScheduler() throws Exception {
}
} else {
try {
// calculate whether the trigger can be triggered in the future
Calendar cal = null;
if (trigger.getCalendarName() != null) {
cal = scheduler.getCalendar(trigger.getCalendarName());
}
OperableTrigger ot = (OperableTrigger) trigger;
Date ft = ot.computeFirstFireTime(cal);
if (ft == null && ignoreExpiredNextFireTime) {
if (hasTriggerExpired(scheduler, trigger)) {
scheduled = false;
LOG.warn(
"Job {} (cron={}, triggerType={}, jobClass={}) not scheduled, because it will never fire in the future",
Expand Down Expand Up @@ -433,6 +427,22 @@ private void addJobInScheduler() throws Exception {
jobAdded.set(true);
}

private boolean hasTriggerExpired(Scheduler scheduler, Trigger trigger) throws SchedulerException {
Calendar cal = null;
if (trigger.getCalendarName() != null) {
cal = scheduler.getCalendar(trigger.getCalendarName());
}
OperableTrigger ot = (OperableTrigger) trigger;

// check if current time is past the Trigger EndDate
if (ot.getEndTime() != null && new Date().after(ot.getEndTime())) {
return true;
}
// calculate whether the trigger can be triggered in the future
Date ft = ot.computeFirstFireTime(cal);
return (ft == null && ignoreExpiredNextFireTime);
}

private boolean hasTriggerChanged(Trigger oldTrigger, Trigger newTrigger) {
if (newTrigger instanceof CronTrigger && oldTrigger instanceof CronTrigger) {
CronTrigger newCron = (CronTrigger) newTrigger;
Expand Down Expand Up @@ -471,6 +481,15 @@ private Trigger createTrigger(JobDetail jobDetail) throws Exception {
}
if (cron != null) {
LOG.debug("Creating CronTrigger: {}", cron);
final String startAt = (String) copy.get("startAt");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
if (startAt != null) {
triggerBuilder.startAt(dateFormat.parse(startAt));
}
final String endAt = (String) copy.get("endAt");
if (endAt != null) {
triggerBuilder.endAt(dateFormat.parse(endAt));
}
final String timeZone = (String) copy.get("timeZone");
if (timeZone != null) {
if (ObjectHelper.isNotEmpty(customCalendar)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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.component.quartz;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;

/**
* This test the CronTrigger as a timer endpoint in a route.
*/
public class QuartzCronRouteWithStartDateEndDateTest extends BaseQuartzTest {

@Test
public void testQuartzCronRouteWithStartDateEndDateTest() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:result");
mock.expectedMinimumMessageCount(2);
mock.await(5, TimeUnit.SECONDS);

MockEndpoint.assertIsSatisfied(context);
assertThat(mock.getReceivedExchanges().size() <= 3, CoreMatchers.is(true));
}

@Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
public void configure() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
calendar.add(Calendar.SECOND, 3);
Date startDate = calendar.getTime();
calendar.add(Calendar.SECOND, 2);
Date endDate = calendar.getTime();

// triggers every 1th second at precise 00,01,02,03..59 with startAt and endAt exactly 2 second apart.
// configuration will create a maximum of three messages
fromF("quartz://myGroup/myTimerName?cron=0/1 * * * * ?&trigger.startAt=%s&trigger.endAt=%s",
dateFormat.format(startDate), dateFormat.format(endDate)).to("mock:result");
}
};
}
}

0 comments on commit 2a90c15

Please sign in to comment.