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 28c1c3b0cc8a CAMEL-24055: Fix custom calendar name collision in 
QuartzEndpoint
28c1c3b0cc8a is described below

commit 28c1c3b0cc8a53a15f55d0a5db214950e74942f2
Author: Prasanth Rao <[email protected]>
AuthorDate: Tue Jul 14 21:38:31 2026 +0530

    CAMEL-24055: Fix custom calendar name collision in QuartzEndpoint
    
    Replace the hardcoded "CamelQuartzCustomCalendar" scheduler name with a
    per-endpoint name derived from the trigger key (group + name). When two or
    more routes each configured a customCalendar, the second 
addCalendar(replace=true)
    silently overwrote the first, causing all triggers to use the 
last-registered
    calendar instead of their own.
    
    Custom calendars are now also deleted from the scheduler in doStop() to 
prevent
    stale entries accumulating in JDBC-backed job stores.
    
    Closes #24670
    
    Co-authored-by: Claude <[email protected]>
---
 .../camel/component/quartz/QuartzEndpoint.java     | 20 ++++--
 .../quartz/QuartzCustomCalendarCollisionTest.java  | 78 ++++++++++++++++++++++
 .../quartz/QuartzCustomCalendarFireTest.java       |  5 +-
 .../quartz/QuartzCustomCalendarNoFireTest.java     |  5 +-
 4 files changed, 101 insertions(+), 7 deletions(-)

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 92b9dba5e2ac..7748cdc5d564 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
@@ -310,6 +310,10 @@ public class QuartzEndpoint extends DefaultEndpoint {
         this.customCalendar = customCalendar;
     }
 
+    private String customCalendarName() {
+        return QuartzConstants.QUARTZ_CAMEL_CUSTOM_CALENDAR + "_" + 
triggerKey.getGroup() + "_" + triggerKey.getName();
+    }
+
     @Override
     public Producer createProducer() throws Exception {
         throw new UnsupportedOperationException("Quartz producer is not 
supported.");
@@ -328,8 +332,7 @@ public class QuartzEndpoint extends DefaultEndpoint {
             throw new IllegalArgumentException("Cannot have both options 
deleteJob and pauseJob enabled");
         }
         if (ObjectHelper.isNotEmpty(customCalendar)) {
-            
getComponent().getScheduler().addCalendar(QuartzConstants.QUARTZ_CAMEL_CUSTOM_CALENDAR,
 customCalendar, true,
-                    false);
+            getComponent().getScheduler().addCalendar(customCalendarName(), 
customCalendar, true, false);
         }
         addJobInScheduler();
     }
@@ -352,6 +355,13 @@ public class QuartzEndpoint extends DefaultEndpoint {
                 scheduler.unscheduleJob(triggerKey);
 
                 jobAdded.set(false);
+                if (ObjectHelper.isNotEmpty(customCalendar)) {
+                    try {
+                        scheduler.deleteCalendar(customCalendarName());
+                    } catch (SchedulerException e) {
+                        LOG.warn("Could not delete calendar {}: {}", 
customCalendarName(), e.getMessage(), e);
+                    }
+                }
             }
         } else if (pauseJob) {
             pauseTrigger();
@@ -501,7 +511,7 @@ public class QuartzEndpoint extends DefaultEndpoint {
                             .withSchedule(cronSchedule(cron)
                                     
.withMisfireHandlingInstructionFireAndProceed()
                                     
.inTimeZone(TimeZone.getTimeZone(timeZone)))
-                            
.modifiedByCalendar(QuartzConstants.QUARTZ_CAMEL_CUSTOM_CALENDAR);
+                            .modifiedByCalendar(customCalendarName());
                 } else {
                     triggerBuilder
                             .withSchedule(cronSchedule(cron)
@@ -514,7 +524,7 @@ public class QuartzEndpoint extends DefaultEndpoint {
                     triggerBuilder
                             .withSchedule(cronSchedule(cron)
                                     
.withMisfireHandlingInstructionFireAndProceed())
-                            
.modifiedByCalendar(QuartzConstants.QUARTZ_CAMEL_CUSTOM_CALENDAR);
+                            .modifiedByCalendar(customCalendarName());
                 } else {
                     triggerBuilder
                             .withSchedule(cronSchedule(cron)
@@ -547,7 +557,7 @@ public class QuartzEndpoint extends DefaultEndpoint {
                 triggerBuilder
                         
.withSchedule(simpleSchedule().withMisfireHandlingInstructionFireNow()
                                 
.withRepeatCount(repeat).withIntervalInMilliseconds(interval))
-                        
.modifiedByCalendar(QuartzConstants.QUARTZ_CAMEL_CUSTOM_CALENDAR);
+                        .modifiedByCalendar(customCalendarName());
             } else {
                 triggerBuilder
                         
.withSchedule(simpleSchedule().withMisfireHandlingInstructionFireNow()
diff --git 
a/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCustomCalendarCollisionTest.java
 
b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCustomCalendarCollisionTest.java
new file mode 100644
index 000000000000..65a51e77d5f4
--- /dev/null
+++ 
b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCustomCalendarCollisionTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.Date;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+import org.quartz.Calendar;
+import org.quartz.Scheduler;
+import org.quartz.impl.calendar.HolidayCalendar;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+
+/**
+ * Regression test: two endpoints with different customCalendars must each 
register under a unique scheduler name so
+ * that the second addCalendar() does not overwrite the first.
+ */
+public class QuartzCustomCalendarCollisionTest extends BaseQuartzTest {
+
+    @BindToRegistry("calendarA")
+    public HolidayCalendar loadCalendarA() {
+        HolidayCalendar cal = new HolidayCalendar();
+        // Exclude today — this calendar should suppress triggers
+        cal.addExcludedDate(new Date());
+        return cal;
+    }
+
+    @BindToRegistry("calendarB")
+    public HolidayCalendar loadCalendarB() {
+        // No excluded dates — always-open calendar
+        return new HolidayCalendar();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                
from("quartz://TimerA?customCalendar=#calendarA&cron=05+00+00+*+*+?").routeId("routeA").to("mock:a");
+                
from("quartz://TimerB?customCalendar=#calendarB&cron=05+00+00+*+*+?").routeId("routeB").to("mock:b");
+            }
+        };
+    }
+
+    @Test
+    public void testEachEndpointRegistersItsOwnCalendar() throws Exception {
+        QuartzComponent component = context.getComponent("quartz", 
QuartzComponent.class);
+        Scheduler scheduler = component.getScheduler();
+
+        QuartzEndpoint endpointA = (QuartzEndpoint) 
context.getRoute("routeA").getConsumer().getEndpoint();
+        QuartzEndpoint endpointB = (QuartzEndpoint) 
context.getRoute("routeB").getConsumer().getEndpoint();
+
+        Calendar calA = 
scheduler.getCalendar(QuartzConstants.QUARTZ_CAMEL_CUSTOM_CALENDAR + "_" + 
endpointA.getGroupName()
+                                              + "_" + 
endpointA.getTriggerName());
+        Calendar calB = 
scheduler.getCalendar(QuartzConstants.QUARTZ_CAMEL_CUSTOM_CALENDAR + "_" + 
endpointB.getGroupName()
+                                              + "_" + 
endpointB.getTriggerName());
+
+        assertNotNull(calA, "Calendar for TimerA must be registered under its 
own unique name");
+        assertNotNull(calB, "Calendar for TimerB must be registered under its 
own unique name");
+        assertNotSame(calA, calB, "Each endpoint must register a distinct 
calendar object");
+    }
+}
diff --git 
a/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCustomCalendarFireTest.java
 
b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCustomCalendarFireTest.java
index 5147f4562c5f..38036cb95df1 100644
--- 
a/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCustomCalendarFireTest.java
+++ 
b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCustomCalendarFireTest.java
@@ -42,7 +42,10 @@ public class QuartzCustomCalendarFireTest extends 
BaseQuartzTest {
         QuartzComponent component = context.getComponent("quartz", 
QuartzComponent.class);
         Scheduler scheduler = component.getScheduler();
 
-        Calendar c = 
scheduler.getCalendar(QuartzConstants.QUARTZ_CAMEL_CUSTOM_CALENDAR);
+        QuartzEndpoint endpoint = (QuartzEndpoint) 
context.getRoutes().get(0).getConsumer().getEndpoint();
+        String calendarName = QuartzConstants.QUARTZ_CAMEL_CUSTOM_CALENDAR + 
"_" + endpoint.getGroupName() + "_"
+                              + endpoint.getTriggerName();
+        Calendar c = scheduler.getCalendar(calendarName);
         Date now = new Date();
         java.util.Calendar tomorrow = java.util.Calendar.getInstance();
         tomorrow.setTime(now);
diff --git 
a/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCustomCalendarNoFireTest.java
 
b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCustomCalendarNoFireTest.java
index cd08198cecac..dbe806cfd19e 100644
--- 
a/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCustomCalendarNoFireTest.java
+++ 
b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzCustomCalendarNoFireTest.java
@@ -42,7 +42,10 @@ public class QuartzCustomCalendarNoFireTest extends 
BaseQuartzTest {
         QuartzComponent component = context.getComponent("quartz", 
QuartzComponent.class);
         Scheduler scheduler = component.getScheduler();
 
-        Calendar c = 
scheduler.getCalendar(QuartzConstants.QUARTZ_CAMEL_CUSTOM_CALENDAR);
+        QuartzEndpoint endpoint = (QuartzEndpoint) 
context.getRoutes().get(0).getConsumer().getEndpoint();
+        String calendarName = QuartzConstants.QUARTZ_CAMEL_CUSTOM_CALENDAR + 
"_" + endpoint.getGroupName() + "_"
+                              + endpoint.getTriggerName();
+        Calendar c = scheduler.getCalendar(calendarName);
         Date now = new Date();
         java.util.Calendar tomorrow = java.util.Calendar.getInstance();
         tomorrow.setTime(now);

Reply via email to