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";

Reply via email to