From f9a48cb435244400c7c3da06fa9c379584eba898 Mon Sep 17 00:00:00 2001 From: Prasanth Rao Date: Mon, 20 Nov 2023 13:12:55 +0530 Subject: [PATCH 1/3] CAMEL-20115: Support for Start Date and End Date in camel-quartz --- .../src/main/docs/quartz-component.adoc | 16 ++++++ .../camel/component/quartz/CamelJob.java | 19 +++++++ .../component/quartz/QuartzEndpoint.java | 33 +++++++++--- ...artzCronRouteWithStartDateEndDateTest.java | 53 +++++++++++++++++++ 4 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCronRouteWithStartDateEndDateTest.java diff --git a/components/camel-quartz/src/main/docs/quartz-component.adoc b/components/camel-quartz/src/main/docs/quartz-component.adoc index 69e5df831bfb3..04997f7c8a1ee 100644 --- a/components/camel-quartz/src/main/docs/quartz-component.adoc +++ b/components/camel-quartz/src/main/docs/quartz-component.adoc @@ -177,6 +177,22 @@ 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 as TimeInMillis as follows: + +---- +quartz://groupName/timerName?cron=0+0/5+12-18+?+*+MON-FRI&trigger.startAt=1700452232554 +---- + +== Specifying end date + +The Quartz Scheduler allows you to configure end date per trigger. You can provide the end date as TimeInMillis as follows: + +---- +quartz://groupName/timerName?cron=0+0/5+12-18+?+*+MON-FRI&trigger.endAt=1700452232554 +---- + == 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 13e3181dca348..a0cc14d7b7452 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 @@ -16,6 +16,7 @@ */ package org.apache.camel.component.quartz; +import java.util.Date; import java.util.Collection; 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,18 @@ public void execute(JobExecutionContext context) throws JobExecutionException { } } + private boolean hasTriggerExpired(JobExecutionContext context) { + Date fireTime = context.getFireTime(); + if (context.getTrigger().getStartTime() != null && fireTime.before(context.getTrigger().getStartTime())) { + // Trigger invalid as Fire Time is before Start Time + return true; + } else if (context.getTrigger().getEndTime() != null && fireTime.after(context.getTrigger().getEndTime())) { + // Trigger expired as Fire Time is after End Time + return true; + } + return false; + } + 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 3474b2fe362e1..a2eb4899d91ac 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 @@ -389,14 +389,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", @@ -436,6 +429,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; @@ -474,6 +483,14 @@ private Trigger createTrigger(JobDetail jobDetail) throws Exception { } if (cron != null) { LOG.debug("Creating CronTrigger: {}", cron); + final String startAt = (String) copy.get("startAt"); + if (startAt != null) { + triggerBuilder.startAt(new Date(Long.parseLong(startAt))); + } + final String endAt = (String) copy.get("endAt"); + if (endAt != null) { + triggerBuilder.endAt(new Date(Long.parseLong(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 0000000000000..c054a3895d267 --- /dev/null +++ b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCronRouteWithStartDateEndDateTest.java @@ -0,0 +1,53 @@ +/* + * 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.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() { + // 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 + from("quartz://myGroup/myTimerName?cron=0/1 * * * * ?&trigger.startAt=" + System.currentTimeMillis() + + "&trigger.endAt=" + (System.currentTimeMillis() + 2000)).to("mock:result"); + } + }; + } +} \ No newline at end of file From d9d683b8f2c6ac54ec39e527692c85412c368b3e Mon Sep 17 00:00:00 2001 From: Prasanth Rao Date: Mon, 20 Nov 2023 15:31:41 +0530 Subject: [PATCH 2/3] Fix: Trigger Expiry check in CamelJob Expiry check missed the case when both start and end time are provided --- .../camel/component/quartz/CamelJob.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) 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 a0cc14d7b7452..b54ca895884d2 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 @@ -101,16 +101,23 @@ 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(); - if (context.getTrigger().getStartTime() != null && fireTime.before(context.getTrigger().getStartTime())) { - // Trigger invalid as Fire Time is before Start Time - return true; - } else if (context.getTrigger().getEndTime() != null && fireTime.after(context.getTrigger().getEndTime())) { - // Trigger expired as Fire Time is after End Time - return true; - } - return false; + + // Trigger valid if Start Time is null or before Fire Time + boolean validStartTime = context.getTrigger().getStartTime() == null || fireTime.after(context.getTrigger().getStartTime()); + + // Trigger valid if End Time is null or after Fire Time + boolean validEndTime = context.getTrigger().getEndTime() == null || fireTime.before(context.getTrigger().getEndTime()); + + return !(validStartTime && validEndTime); } protected CamelContext getCamelContext(JobExecutionContext context) throws JobExecutionException { From a8dfe74b53c0e871cd448ed6ffa3179b8ab6fb0e Mon Sep 17 00:00:00 2001 From: Prasanth Rao Date: Mon, 20 Nov 2023 21:26:07 +0530 Subject: [PATCH 3/3] Fix for exact match in CamelJob for fireTime --- .../apache/camel/component/quartz/CamelJob.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 b54ca895884d2..4587513c6e119 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 @@ -16,8 +16,8 @@ */ package org.apache.camel.component.quartz; -import java.util.Date; import java.util.Collection; +import java.util.Date; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicReference; @@ -59,7 +59,7 @@ public void execute(JobExecutionContext context) throws JobExecutionException { context.getTrigger().getStartTime(), context.getTrigger().getEndTime(), context); return; } - + if (LOG.isDebugEnabled()) { LOG.debug("Running CamelJob jobExecutionContext={}", context); } @@ -104,7 +104,7 @@ public void execute(JobExecutionContext context) throws JobExecutionException { /** * Validates if the Fire Time lies within the Start Time and End Time * - * @param context + * @param context * * @return */ @@ -112,10 +112,14 @@ private boolean hasTriggerExpired(JobExecutionContext context) { Date fireTime = context.getFireTime(); // Trigger valid if Start Time is null or before Fire Time - boolean validStartTime = context.getTrigger().getStartTime() == null || fireTime.after(context.getTrigger().getStartTime()); + 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 - boolean validEndTime = context.getTrigger().getEndTime() == null || fireTime.before(context.getTrigger().getEndTime()); + Date endTime = context.getTrigger().getEndTime(); + boolean validEndTime + = context.getTrigger().getEndTime() == null || fireTime.equals(endTime) || fireTime.before(endTime); return !(validStartTime && validEndTime); }