This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 4803a48533bf CAMEL-25354: camel-quartz, camel-spring - accept the
five-part cron schedules of the cron component (#27529)
4803a48533bf is described below
commit 4803a48533bf6da9e9d111cd6ed2994821dada08
Author: allthingssecurity <[email protected]>
AuthorDate: Thu Oct 8 14:16:13 2026 +0530
CAMEL-25354: camel-quartz, camel-spring - accept the five-part cron
schedules of the cron component (#27529)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../apache/camel/catalog/docs/cron-component.adoc | 5 ++
.../camel-cron/src/main/docs/cron-component.adoc | 5 ++
.../quartz/cron/CamelQuartzCronService.java | 12 +++
.../quartz/cron/QuartzCronMappingTest.java | 29 +++++++
.../component/cron/SpringCronFivePartsTest.java | 93 ++++++++++++++++++++++
.../component/cron/CamelSpringCronService.java | 20 ++++-
6 files changed, 163 insertions(+), 1 deletion(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cron-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cron-component.adoc
index fcbee91fc7c9..8fa8f49cd0c9 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cron-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cron-component.adoc
@@ -110,6 +110,11 @@ Breaking down the parts in the schedule expression(in
order):
Schedule expressions can be made of five to seven parts. When expressions are
composed of six parts, the first items is the _Seconds_ part (and year is
considered missing).
+A five-part expression can also use the Unix cron syntax, with `*` in both day
fields (for example `*/5 * * * *`).
+The day of month and the day of week cannot both be given. With camel-quartz,
give the day of week by name (such as
+`MON-FRI`): a number is used as in Quartz, where Sunday is 1 (0 in Unix), and
needs `?` in the day of month. With
+camel-spring, the day of week cannot use `*/n`.
+
Other valid examples of schedule expressions are:
- `0/2 * * * ?` (Five parts, an event every two minutes)
diff --git a/components/camel-cron/src/main/docs/cron-component.adoc
b/components/camel-cron/src/main/docs/cron-component.adoc
index fcbee91fc7c9..8fa8f49cd0c9 100644
--- a/components/camel-cron/src/main/docs/cron-component.adoc
+++ b/components/camel-cron/src/main/docs/cron-component.adoc
@@ -110,6 +110,11 @@ Breaking down the parts in the schedule expression(in
order):
Schedule expressions can be made of five to seven parts. When expressions are
composed of six parts, the first items is the _Seconds_ part (and year is
considered missing).
+A five-part expression can also use the Unix cron syntax, with `*` in both day
fields (for example `*/5 * * * *`).
+The day of month and the day of week cannot both be given. With camel-quartz,
give the day of week by name (such as
+`MON-FRI`): a number is used as in Quartz, where Sunday is 1 (0 in Unix), and
needs `?` in the day of month. With
+camel-spring, the day of week cannot use `*/n`.
+
Other valid examples of schedule expressions are:
- `0/2 * * * ?` (Five parts, an event every two minutes)
diff --git
a/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/cron/CamelQuartzCronService.java
b/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/cron/CamelQuartzCronService.java
index f78cf0e202bd..c8cbb1d015fd 100644
---
a/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/cron/CamelQuartzCronService.java
+++
b/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/cron/CamelQuartzCronService.java
@@ -48,6 +48,18 @@ public class CamelQuartzCronService implements
CamelCronService, CamelContextAwa
private String convertSchedule(String schedule) {
String[] parts = schedule.split("\\s");
if (parts.length == 5) {
+ // Unix cron syntax: Quartz cannot match both the day of month and
the day of week, one of them must be ?
+ // and * (any day) in the other one is ? in Quartz. A day of week
given by number is left as it is, since
+ // Quartz counts the days from SUN=1 and Unix from SUN=0
+ if (!"?".equals(parts[2]) && !"?".equals(parts[4])) {
+ if ("*".equals(parts[4])) {
+ parts[4] = "?";
+ schedule = String.join(" ", parts);
+ } else if ("*".equals(parts[2]) &&
parts[4].chars().noneMatch(Character::isDigit)) {
+ parts[2] = "?";
+ schedule = String.join(" ", parts);
+ }
+ }
// Seconds are mandatory in Quartz, let's add them back
return "0 " + schedule;
}
diff --git
a/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/cron/QuartzCronMappingTest.java
b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/cron/QuartzCronMappingTest.java
index c533be2ca2aa..c98021251eee 100644
---
a/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/cron/QuartzCronMappingTest.java
+++
b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/cron/QuartzCronMappingTest.java
@@ -56,6 +56,35 @@ public class QuartzCronMappingTest extends BaseQuartzTest {
assertEquals("0/2 * * * * ?",
getQuartzEndpoint(context.getRoute("cron")).getCron());
}
+ @Test
+ public void test5PartsUnixCronPattern() throws Exception {
+ // every five minutes, in Unix cron syntax (no ? in the day fields)
+ assertEquals("0 */5 * * * ?", quartzCron("*/5 * * * *"));
+ }
+
+ @Test
+ public void test5PartsUnixCronPatternDayOfWeekNames() throws Exception {
+ assertEquals("0 0 9 ? * MON-FRI", quartzCron("0 9 * * MON-FRI"));
+ }
+
+ @Test
+ public void test5PartsUnixCronPatternDayOfMonth() throws Exception {
+ assertEquals("0 30 6 1,15 * ?", quartzCron("30 6 1,15 * *"));
+ }
+
+ private String quartzCron(String schedule) throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("cron://myName?schedule=" + schedule)
+ .id("cron")
+ .to("mock:result");
+ }
+ });
+ context.start();
+ return getQuartzEndpoint(context.getRoute("cron")).getCron();
+ }
+
private QuartzEndpoint getQuartzEndpoint(Route route) {
Endpoint endpoint = route.getEndpoint();
while (endpoint instanceof DelegateEndpoint) {
diff --git
a/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/component/cron/SpringCronFivePartsTest.java
b/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/component/cron/SpringCronFivePartsTest.java
new file mode 100644
index 000000000000..5bf5f0fa9644
--- /dev/null
+++
b/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/component/cron/SpringCronFivePartsTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.cron;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.DelegateEndpoint;
+import org.apache.camel.Endpoint;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.support.ScheduledPollEndpoint;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The cron component documents schedules of five parts (no seconds), such as
0/2 * * * ?; the Spring implementation
+ * must accept them like the Quartz one does.
+ */
+class SpringCronFivePartsTest extends ContextTestSupport {
+
+ @Override
+ public boolean isUseRouteBuilder() {
+ return false;
+ }
+
+ @Test
+ void testFivePartsSchedule() throws Exception {
+ assertEquals("0 0/2 * * * ?", springCron("0/2 * * * ?"));
+ }
+
+ @Test
+ void testFivePartsUnixSchedule() throws Exception {
+ assertEquals("0 */5 * * * *", springCron("*/5 * * * *"));
+ }
+
+ @Test
+ void testFivePartsUnixScheduleDayOfWeekNames() throws Exception {
+ assertEquals("0 0 9 * * MON-FRI", springCron("0 9 * * MON-FRI"));
+ }
+
+ @Test
+ void testFivePartsUnixScheduleBothDaysRejected() {
+ // Unix cron fires on the 1st, the 15th and every Monday; Spring would
fire only on a Monday that is the 1st or
+ // the 15th, so the schedule fails as before instead of firing on
other days
+ Exception e = assertThrows(Exception.class, () -> springCron("30 6
1,15 * MON"));
+ assertTrue(rootCauseMessage(e).contains("would fire on other days"),
rootCauseMessage(e));
+ }
+
+ @Test
+ void testFivePartsUnixScheduleDayOfWeekStepRejected() {
+ // Unix */2 is SUN, TUE, THU, SAT; Spring counts from Monday
+ Exception e = assertThrows(Exception.class, () -> springCron("0 9 * *
*/2"));
+ assertTrue(rootCauseMessage(e).contains("would fire on other days"),
rootCauseMessage(e));
+ }
+
+ private static String rootCauseMessage(Throwable e) {
+ while (e.getCause() != null) {
+ e = e.getCause();
+ }
+ return e.getMessage();
+ }
+
+ private String springCron(String schedule) throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("cron:tab?schedule=" +
schedule).routeId("cron").to("mock:result");
+ }
+ });
+ context.start();
+
+ Endpoint endpoint = context.getRoute("cron").getEndpoint();
+ while (endpoint instanceof DelegateEndpoint delegate) {
+ endpoint = delegate.getEndpoint();
+ }
+ return (String) ((ScheduledPollEndpoint)
endpoint).getSchedulerProperties().get("cron");
+ }
+}
diff --git
a/components/camel-spring-parent/camel-spring/src/main/java/org/apache/camel/component/cron/CamelSpringCronService.java
b/components/camel-spring-parent/camel-spring/src/main/java/org/apache/camel/component/cron/CamelSpringCronService.java
index 723fa1ee4064..295e91fb3a47 100644
---
a/components/camel-spring-parent/camel-spring/src/main/java/org/apache/camel/component/cron/CamelSpringCronService.java
+++
b/components/camel-spring-parent/camel-spring/src/main/java/org/apache/camel/component/cron/CamelSpringCronService.java
@@ -39,11 +39,29 @@ public class CamelSpringCronService implements
CamelCronService, CamelContextAwa
SpringCronEndpoint cronEndpoint = new SpringCronEndpoint(uri,
cronComponent);
Map<String, Object> options = new HashMap<>();
options.put("scheduler", "spring");
- options.put("scheduler.cron", configuration.getSchedule());
+ String schedule = configuration.getSchedule();
+ String[] parts = schedule.split("\\s");
+ if (parts.length == 5) {
+ // Unix cron syntax: a day of month and a day of week both given
match either day (Spring: both days), and
+ // */n in the day of week counts from Sunday (Spring: from
Monday), so such schedules are not converted
+ if ((!isAnyDay(parts[2]) && !isAnyDay(parts[4])) ||
parts[4].startsWith("*/")) {
+ throw new IllegalArgumentException(
+ "The five-part schedule " + schedule + " would fire on
other days with the spring cron service:"
+ + " use * or ? in the day
of month or in the day of week, and no */n"
+ + " in the day of week, or
a schedule with seconds");
+ }
+ // the seconds are optional in the cron component but mandatory in
Spring
+ schedule = "0 " + schedule;
+ }
+ options.put("scheduler.cron", schedule);
cronEndpoint.configureProperties(options);
return cronEndpoint;
}
+ private static boolean isAnyDay(String field) {
+ return "*".equals(field) || "?".equals(field);
+ }
+
@Override
public String getId() {
return "spring";