This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch camel-3.x in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-3.x by this push: new b2f0e66c77b CAMEL-20115: Support for Start Date and End Date in camel-quartz (#12098) b2f0e66c77b is described below commit b2f0e66c77b11aa6c1c6af13c002588290c631e4 Author: Prasanth Rao <uvce.prasa...@gmail.com> AuthorDate: Fri Nov 24 19:38:03 2023 +0530 CAMEL-20115: Support for Start Date and End Date in camel-quartz (#12098) * 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 <prasanth....@sap.com> --- .../src/main/docs/quartz-component.adoc | 21 +++++++ .../apache/camel/component/quartz/CamelJob.java | 30 ++++++++++ .../camel/component/quartz/QuartzEndpoint.java | 35 +++++++++--- .../QuartzCronRouteWithStartDateEndDateTest.java | 65 ++++++++++++++++++++++ 4 files changed, 143 insertions(+), 8 deletions(-) diff --git a/components/camel-quartz/src/main/docs/quartz-component.adoc b/components/camel-quartz/src/main/docs/quartz-component.adoc index 28ba9e8522a..03ffb9507e2 100644 --- a/components/camel-quartz/src/main/docs/quartz-component.adoc +++ b/components/camel-quartz/src/main/docs/quartz-component.adoc @@ -163,6 +163,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 diff --git a/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/CamelJob.java b/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/CamelJob.java index 13e3181dca3..4587513c6e1 100644 --- a/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/CamelJob.java +++ b/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/CamelJob.java @@ -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; @@ -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); } @@ -94,6 +101,29 @@ public class CamelJob implements Job, InterruptableJob { } } + /** + * 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); diff --git a/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/QuartzEndpoint.java b/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/QuartzEndpoint.java index 1f905afa12f..6f242e14273 100644 --- a/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/QuartzEndpoint.java +++ b/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/QuartzEndpoint.java @@ -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; @@ -382,14 +383,7 @@ public class QuartzEndpoint extends DefaultEndpoint { } } 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", @@ -433,6 +427,22 @@ public class QuartzEndpoint extends DefaultEndpoint { 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; @@ -471,6 +481,15 @@ public class QuartzEndpoint extends DefaultEndpoint { } 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)) { diff --git a/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCronRouteWithStartDateEndDateTest.java b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCronRouteWithStartDateEndDateTest.java new file mode 100644 index 00000000000..b39084a2073 --- /dev/null +++ b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCronRouteWithStartDateEndDateTest.java @@ -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"); + } + }; + } +}