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 7fae76281402 CAMEL-26289: camel-quartz - with deleteJob=false a 
trigger paused by an earlier endpoint is resumed when the route starts (#28642)
7fae76281402 is described below

commit 7fae7628140288960e29221e7b3d21645f5e242d
Author: allthingssecurity <[email protected]>
AuthorDate: Fri Oct 30 14:24:12 2026 +0530

    CAMEL-26289: camel-quartz - with deleteJob=false a trigger paused by an 
earlier endpoint is resumed when the route starts (#28642)
    
    …an earlier endpoint is resumed when the route starts
    
    The consumer pauses its trigger when it stops, and the endpoint resumed it 
only
    when it had paused it itself (its own jobPaused flag); an existing unchanged
    trigger was kept as it was. So with deleteJob=false (as with pauseJob=true) 
a
    trigger stored paused before a restart with a persistent job store, or 
paused by
    the endpoint of a route that was removed and added again, stayed paused and 
the
    route never fired again. Such an endpoint now takes over the paused state 
of an
    existing trigger, so its consumer resumes it when it starts. A clustered job
    store is left as it is, as Camel does not pause triggers there.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../camel/catalog/docs/quartz-component.adoc       |  12 ++
 .../src/main/docs/quartz-component.adoc            |  12 ++
 .../camel/component/quartz/QuartzConstants.java    |   3 +
 .../camel/component/quartz/QuartzEndpoint.java     |  57 ++++++++-
 .../quartz/QuartzPauseJobRouteAddedAgainTest.java  | 131 +++++++++++++++++++++
 ...uartzPersistentStorePauseJobRestartAppTest.java |  82 +++++++++++++
 ...ingQuartzPersistentStorePauseJobRestartTest.xml |  74 ++++++++++++
 .../SpringQuartzPersistentStorePauseJobTest.xml    |  74 ++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  11 ++
 9 files changed, 455 insertions(+), 1 deletion(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/quartz-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/quartz-component.adoc
index d81134749eb2..e4db994a2203 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/quartz-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/quartz-component.adoc
@@ -101,6 +101,18 @@ to keep running on the other nodes in the cluster.
 NOTE: When running in clustered node, no checking is done to ensure
 unique job name/group for endpoints.
 
+=== Keeping the trigger when the route stops
+
+With `deleteJob=false` (also with `pauseJob=true`), the route keeps its 
trigger in the scheduler when it stops, and
+pauses it. It marks the trigger as paused by Camel, with the key 
`CamelQuartzTriggerPausedByCamel` in the `JobDataMap`
+of its job, so that a route with this trigger resumes it when it starts: the 
same route started again, a route that
+was removed and added again, or the route after a restart of the application 
with a persistent job store.
+
+A trigger that was paused outside Camel (with the Quartz API or JMX, for 
example while the route runs) has no such
+mark, and stays paused when the route starts, also when the same route is 
stopped and started again.
+A pre-configured trigger that you reuse with the Camel URI is resumed only if 
Camel paused it. To keep a trigger that
+Camel paused from firing, keep the route stopped.
+
 === Message Headers
 
 Camel adds the getters from the Quartz Execution Context as header
diff --git a/components/camel-quartz/src/main/docs/quartz-component.adoc 
b/components/camel-quartz/src/main/docs/quartz-component.adoc
index d81134749eb2..e4db994a2203 100644
--- a/components/camel-quartz/src/main/docs/quartz-component.adoc
+++ b/components/camel-quartz/src/main/docs/quartz-component.adoc
@@ -101,6 +101,18 @@ to keep running on the other nodes in the cluster.
 NOTE: When running in clustered node, no checking is done to ensure
 unique job name/group for endpoints.
 
+=== Keeping the trigger when the route stops
+
+With `deleteJob=false` (also with `pauseJob=true`), the route keeps its 
trigger in the scheduler when it stops, and
+pauses it. It marks the trigger as paused by Camel, with the key 
`CamelQuartzTriggerPausedByCamel` in the `JobDataMap`
+of its job, so that a route with this trigger resumes it when it starts: the 
same route started again, a route that
+was removed and added again, or the route after a restart of the application 
with a persistent job store.
+
+A trigger that was paused outside Camel (with the Quartz API or JMX, for 
example while the route runs) has no such
+mark, and stays paused when the route starts, also when the same route is 
stopped and started again.
+A pre-configured trigger that you reuse with the Camel URI is resumed only if 
Camel paused it. To keep a trigger that
+Camel paused from firing, keep the route stopped.
+
 === Message Headers
 
 Camel adds the getters from the Quartz Execution Context as header
diff --git 
a/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/QuartzConstants.java
 
b/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/QuartzConstants.java
index c3cff268e930..70dedaa282ca 100644
--- 
a/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/QuartzConstants.java
+++ 
b/components/camel-quartz/src/main/java/org/apache/camel/component/quartz/QuartzConstants.java
@@ -37,6 +37,9 @@ public final class QuartzConstants {
 
     public static final String QUARTZ_CAMEL_CUSTOM_CALENDAR = 
"CamelQuartzCustomCalendar";
 
+    // the key in the JobDataMap of the job of a trigger that a Camel endpoint 
paused when its consumer stopped
+    public static final String QUARTZ_TRIGGER_PAUSED_BY_CAMEL = 
"CamelQuartzTriggerPausedByCamel";
+
     private QuartzConstants() {
         // Utility class
     }
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 1b71b5f21b8c..82124b0920da 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
@@ -44,6 +44,7 @@ import org.quartz.Calendar;
 import org.quartz.CronTrigger;
 import org.quartz.Job;
 import org.quartz.JobBuilder;
+import org.quartz.JobDataMap;
 import org.quartz.JobDetail;
 import org.quartz.ObjectAlreadyExistsException;
 import org.quartz.Scheduler;
@@ -399,6 +400,20 @@ public class QuartzEndpoint extends DefaultEndpoint {
             if (hasTriggerChanged(oldTrigger, trigger)) {
                 scheduler.rescheduleJob(triggerKey, trigger);
             }
+            // the consumer pauses the trigger when it stops, and a trigger 
that is not deleted then (deleteJob=false, as
+            // with pauseJob=true) stays paused: paused by the endpoint of a 
route that was removed and is added again,
+            // or before a restart with a persistent job store. This endpoint 
resumes it when its consumer starts, as it
+            // does for a trigger it paused itself, if a Camel endpoint paused 
it (see pauseTrigger): a trigger paused
+            // outside Camel (the Quartz API, JMX) stays paused. A clustered 
job store is left as it is, as no trigger
+            // is paused there
+            if (!deleteJob && !scheduler.getMetaData().isJobStoreClustered()) {
+                if (scheduler.getTriggerState(triggerKey) == 
Trigger.TriggerState.PAUSED) {
+                    jobPaused.set(isPausedByCamel(scheduler));
+                } else {
+                    // resumed outside Camel meanwhile (or stopped between the 
marker and the pause)
+                    markPausedByCamel(scheduler, false);
+                }
+            }
         } else {
             try {
                 if (hasTriggerExpired(scheduler, trigger)) {
@@ -629,10 +644,21 @@ public class QuartzEndpoint extends DefaultEndpoint {
             return;
         }
 
-        jobPaused.set(true);
         if (!scheduler.isShutdown()) {
+            if (scheduler.getTriggerState(triggerKey) == 
Trigger.TriggerState.PAUSED) {
+                // paused outside Camel (the Quartz API, JMX) while the route 
ran: not a pause of Camel, so neither this
+                // endpoint nor the next one with this trigger resumes it
+                LOG.debug("Trigger {} is paused already, not by Camel", 
triggerKey);
+                return;
+            }
+            jobPaused.set(true);
             LOG.info("Pausing trigger {}", triggerKey);
+            // marked first, so the next endpoint with this trigger (the route 
added again, or started again with a
+            // persistent job store) resumes it
+            markPausedByCamel(scheduler, true);
             scheduler.pauseTrigger(triggerKey);
+        } else {
+            jobPaused.set(true);
         }
     }
 
@@ -646,9 +672,38 @@ public class QuartzEndpoint extends DefaultEndpoint {
         if (scheduler != null) {
             LOG.info("Resuming trigger {}", triggerKey);
             scheduler.resumeTrigger(triggerKey);
+            markPausedByCamel(scheduler, false);
         }
     }
 
+    private boolean isPausedByCamel(Scheduler scheduler) throws 
SchedulerException {
+        JobDetail job = jobOfTrigger(scheduler);
+        return job != null && 
job.getJobDataMap().containsKey(QuartzConstants.QUARTZ_TRIGGER_PAUSED_BY_CAMEL);
+    }
+
+    // the marker is in the JobDataMap of the job: the one of the trigger can 
only be changed by rescheduling it, which
+    // computes its fire times again
+    private void markPausedByCamel(Scheduler scheduler, boolean paused) throws 
SchedulerException {
+        JobDetail job = jobOfTrigger(scheduler);
+        if (job == null || 
job.getJobDataMap().containsKey(QuartzConstants.QUARTZ_TRIGGER_PAUSED_BY_CAMEL) 
== paused) {
+            return;
+        }
+        JobDataMap data = new JobDataMap(job.getJobDataMap());
+        if (paused) {
+            // a String, as a JDBC job store with useProperties=true only 
stores String values
+            data.put(QuartzConstants.QUARTZ_TRIGGER_PAUSED_BY_CAMEL, "true");
+        } else {
+            data.remove(QuartzConstants.QUARTZ_TRIGGER_PAUSED_BY_CAMEL);
+        }
+        // replacing the job keeps its triggers and their state
+        scheduler.addJob(job.getJobBuilder().setJobData(data).build(), true, 
true);
+    }
+
+    private JobDetail jobOfTrigger(Scheduler scheduler) throws 
SchedulerException {
+        Trigger trigger = scheduler.getTrigger(triggerKey);
+        return trigger != null ? scheduler.getJobDetail(trigger.getJobKey()) : 
null;
+    }
+
     public void onConsumerStart(QuartzConsumer quartzConsumer) throws 
Exception {
         this.processor = quartzConsumer.getAsyncProcessor();
         if (!jobAdded.get()) {
diff --git 
a/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzPauseJobRouteAddedAgainTest.java
 
b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzPauseJobRouteAddedAgainTest.java
new file mode 100644
index 000000000000..2d169e21840d
--- /dev/null
+++ 
b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/QuartzPauseJobRouteAddedAgainTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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 org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+import org.quartz.Scheduler;
+import org.quartz.Trigger;
+import org.quartz.TriggerKey;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * A route that keeps its trigger when it stops (deleteJob=false, also with 
pauseJob=true) pauses the trigger, and it
+ * must fire again when the route is added again (such as a reload), as it 
does when the same route is started again.
+ */
+class QuartzPauseJobRouteAddedAgainTest extends BaseQuartzTest {
+
+    @Test
+    void pauseJobTriggerFiresAgainWhenTheRouteIsAddedAgain() throws Exception {
+        assertFiresAgainWhenAddedAgain("paused", 
"pauseJob=true&deleteJob=false");
+    }
+
+    @Test
+    void keptTriggerFiresAgainWhenTheRouteIsAddedAgain() throws Exception {
+        assertFiresAgainWhenAddedAgain("kept", "deleteJob=false");
+    }
+
+    @Test
+    void triggerFiresAgainWhenTheRouteIsStartedAgain() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:paused");
+        mock.expectedMinimumMessageCount(1);
+        mock.assertIsSatisfied();
+
+        context.getRouteController().stopRoute("paused");
+        context.getRouteController().startRoute("paused");
+
+        mock.reset();
+        mock.expectedMinimumMessageCount(1);
+        mock.assertIsSatisfied();
+    }
+
+    @Test
+    void triggerPausedOutsideCamelStaysPausedWhenTheRouteIsAddedAgain() throws 
Exception {
+        assertPausedOutsideCamelStaysPaused("paused", 
"pauseJob=true&deleteJob=false");
+    }
+
+    @Test
+    void keptTriggerPausedOutsideCamelStaysPausedWhenTheRouteIsAddedAgain() 
throws Exception {
+        assertPausedOutsideCamelStaysPaused("kept", "deleteJob=false");
+    }
+
+    @Test
+    void triggerPausedOutsideCamelStaysPausedWhenTheRouteIsStartedAgain() 
throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:paused");
+        mock.expectedMinimumMessageCount(1);
+        MockEndpoint.assertIsSatisfied(context);
+
+        Scheduler scheduler = context.getComponent("quartz", 
QuartzComponent.class).getScheduler();
+        TriggerKey triggerKey = TriggerKey.triggerKey("paused", "myGroup");
+        scheduler.pauseTrigger(triggerKey);
+        context.getRouteController().stopRoute("paused");
+        context.getRouteController().startRoute("paused");
+
+        assertEquals(Trigger.TriggerState.PAUSED, 
scheduler.getTriggerState(triggerKey));
+    }
+
+    private void assertPausedOutsideCamelStaysPaused(String routeId, String 
options) throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:" + routeId);
+        mock.expectedMinimumMessageCount(1);
+        MockEndpoint.assertIsSatisfied(context);
+
+        // paused with the Quartz API (or JMX) while the route runs, and then 
the route is reloaded: only a trigger that
+        // Camel paused is resumed
+        Scheduler scheduler = context.getComponent("quartz", 
QuartzComponent.class).getScheduler();
+        TriggerKey triggerKey = TriggerKey.triggerKey(routeId, "myGroup");
+        scheduler.pauseTrigger(triggerKey);
+        context.getRouteController().stopRoute(routeId);
+        context.removeRoute(routeId);
+        context.addRoutes(route(routeId, options));
+
+        assertEquals(Trigger.TriggerState.PAUSED, 
scheduler.getTriggerState(triggerKey));
+    }
+
+    private void assertFiresAgainWhenAddedAgain(String routeId, String 
options) throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:" + routeId);
+        mock.expectedMinimumMessageCount(1);
+        mock.assertIsSatisfied();
+
+        context.getRouteController().stopRoute(routeId);
+        context.removeRoute(routeId);
+        context.addRoutes(route(routeId, options));
+
+        // removing the route removed its mock endpoint too, the route added 
again has a new one
+        MockEndpoint again = getMockEndpoint("mock:" + routeId);
+        again.expectedMinimumMessageCount(1);
+        again.assertIsSatisfied();
+    }
+
+    private static RouteBuilder route(String routeId, String options) {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("quartz://myGroup/" + routeId + 
"?trigger.repeatInterval=100&" + options)
+                        .routeId(routeId)
+                        .to("mock:" + routeId);
+            }
+        };
+    }
+
+    @Override
+    protected RoutesBuilder[] createRouteBuilders() {
+        return new RoutesBuilder[] { route("paused", 
"pauseJob=true&deleteJob=false"), route("kept", "deleteJob=false") };
+    }
+}
diff --git 
a/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/SpringQuartzPersistentStorePauseJobRestartAppTest.java
 
b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/SpringQuartzPersistentStorePauseJobRestartAppTest.java
new file mode 100644
index 000000000000..00d84d6e87fb
--- /dev/null
+++ 
b/components/camel-quartz/src/test/java/org/apache/camel/component/quartz/SpringQuartzPersistentStorePauseJobRestartAppTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.Map;
+import java.util.UUID;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.spring.junit6.CamelSpringTestSupport;
+import org.apache.camel.util.IOHelper;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.support.AbstractXmlApplicationContext;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * With a persistent job store, the trigger of a route that keeps it 
(deleteJob=false, also with pauseJob=true) is
+ * stored paused when the application stops, and it must fire again when the 
application starts again.
+ */
+class SpringQuartzPersistentStorePauseJobRestartAppTest {
+
+    // a database and a scheduler of their own for each run, so a rerun 
(surefire) does not meet the tables and the
+    // scheduler of a run that failed
+    private final String runName = getClass().getSimpleName() + "-" + 
UUID.randomUUID();
+
+    @Test
+    void pausedTriggerFiresAgainAfterTheRestart() throws Exception {
+        AbstractXmlApplicationContext app = null;
+        AbstractXmlApplicationContext app2 = null;
+        try {
+            app = newAppContext("SpringQuartzPersistentStorePauseJobTest.xml");
+            app.start();
+            CamelContext camel = app.getBean("camelContext-" + 
getClass().getSimpleName(), CamelContext.class);
+            assertNotNull(camel);
+            MockEndpoint mock = camel.getEndpoint("mock:result", 
MockEndpoint.class);
+            mock.expectedMinimumMessageCount(1);
+            MockEndpoint kept = camel.getEndpoint("mock:kept", 
MockEndpoint.class);
+            kept.expectedMinimumMessageCount(1);
+            MockEndpoint.assertIsSatisfied(camel);
+
+            // the routes stop and pause their triggers in the job store
+            app.stop();
+
+            app2 = 
newAppContext("SpringQuartzPersistentStorePauseJobRestartTest.xml");
+            app2.start();
+            CamelContext camel2 = app2.getBean("camelContext-" + 
getClass().getSimpleName(), CamelContext.class);
+            assertNotNull(camel2);
+            MockEndpoint mock2 = camel2.getEndpoint("mock:result", 
MockEndpoint.class);
+            mock2.expectedMinimumMessageCount(1);
+            MockEndpoint kept2 = camel2.getEndpoint("mock:kept", 
MockEndpoint.class);
+            kept2.expectedMinimumMessageCount(1);
+            MockEndpoint.assertIsSatisfied(camel2);
+
+            app2.stop();
+        } finally {
+            // close the second application first, so that its scheduler shuts 
down properly
+            IOHelper.close(app2, app);
+        }
+    }
+
+    private AbstractXmlApplicationContext newAppContext(String config) {
+        return CamelSpringTestSupport.newAppContext(config, getClass(),
+                Map.of(CamelSpringTestSupport.TEST_CLASS_NAME_PROPERTY, 
getClass().getName(),
+                        
CamelSpringTestSupport.TEST_CLASS_SIMPLE_NAME_PROPERTY, 
getClass().getSimpleName(),
+                        "runName", runName));
+    }
+}
diff --git 
a/components/camel-quartz/src/test/resources/org/apache/camel/component/quartz/SpringQuartzPersistentStorePauseJobRestartTest.xml
 
b/components/camel-quartz/src/test/resources/org/apache/camel/component/quartz/SpringQuartzPersistentStorePauseJobRestartTest.xml
new file mode 100644
index 000000000000..c5f227e64bab
--- /dev/null
+++ 
b/components/camel-quartz/src/test/resources/org/apache/camel/component/quartz/SpringQuartzPersistentStorePauseJobRestartTest.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xmlns:jdbc="http://www.springframework.org/schema/jdbc";
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd
+       http://www.springframework.org/schema/jdbc 
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
+       http://camel.apache.org/schema/spring 
http://camel.apache.org/schema/spring/camel-spring.xsd
+    ">
+
+  <!-- the persistent store for quartz -->
+  <jdbc:embedded-database id="{{testClassSimpleName}}" 
database-name="{{runName}}" type="H2">
+    <!-- do not load script as database already exists -->
+  </jdbc:embedded-database>
+
+  <bean id="quartz" class="org.apache.camel.component.quartz.QuartzComponent">
+    <property name="scheduler" ref="scheduler"/>
+  </bean>
+
+  <!-- jdbc:embedded-database must come before this so the job store exists -->
+  <bean id="scheduler" 
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
+    <property name="dataSource" ref="{{testClassSimpleName}}"/>
+    <property name="autoStartup" value="false"/>
+    <!-- let Camel start -->
+    <property name="schedulerContextAsMap">
+      <!-- hook Camel into Quartz -->
+      <map>
+          <entry key="CamelQuartzCamelContext-camelContext" 
value-ref="camelContext-{{testClassSimpleName}}"/>
+      </map>
+    </property>
+    <property name="quartzProperties">
+      <props>
+        <prop 
key="org.quartz.scheduler.instanceName">myscheduler-{{runName}}</prop>
+        <prop key="org.quartz.scheduler.instanceId">AUTO</prop>
+        <prop key="org.quartz.scheduler.skipUpdateCheck">true</prop>
+        <prop key="org.terracotta.quartz.skipUpdateCheck">true</prop>
+        <prop 
key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.StdJDBCDelegate</prop>
+        <prop key="org.quartz.jobStore.isClustered">false</prop>
+      </props>
+    </property>
+  </bean>
+
+  <camelContext id="camelContext-{{testClassSimpleName}}" 
managementNamePattern="#name#" xmlns="http://camel.apache.org/schema/spring";>
+    <route>
+      <from 
uri="quartz://app/test?trigger.repeatInterval=1000&amp;trigger.repeatCount=-1&amp;stateful=true&amp;pauseJob=true&amp;deleteJob=false"/>
+      <to uri="log:trigger"/>
+      <to uri="mock:result"/>
+    </route>
+    <route>
+      <from 
uri="quartz://app/kept?trigger.repeatInterval=1000&amp;trigger.repeatCount=-1&amp;stateful=true&amp;deleteJob=false"/>
+      <to uri="log:kept"/>
+      <to uri="mock:kept"/>
+    </route>
+  </camelContext>
+
+</beans>
diff --git 
a/components/camel-quartz/src/test/resources/org/apache/camel/component/quartz/SpringQuartzPersistentStorePauseJobTest.xml
 
b/components/camel-quartz/src/test/resources/org/apache/camel/component/quartz/SpringQuartzPersistentStorePauseJobTest.xml
new file mode 100644
index 000000000000..06a8765dcabb
--- /dev/null
+++ 
b/components/camel-quartz/src/test/resources/org/apache/camel/component/quartz/SpringQuartzPersistentStorePauseJobTest.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xmlns:jdbc="http://www.springframework.org/schema/jdbc";
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd
+       http://www.springframework.org/schema/jdbc 
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
+       http://camel.apache.org/schema/spring 
http://camel.apache.org/schema/spring/camel-spring.xsd
+    ">
+
+  <!-- the persistent store for quartz -->
+  <jdbc:embedded-database id="{{testClassSimpleName}}" 
database-name="{{runName}}" type="H2">
+    <jdbc:script location="classpath:tables_h2.sql"/>
+  </jdbc:embedded-database>
+
+  <bean id="quartz" class="org.apache.camel.component.quartz.QuartzComponent">
+    <property name="scheduler" ref="scheduler"/>
+  </bean>
+
+  <!-- jdbc:embedded-database must come before this so the job store exists -->
+  <bean id="scheduler" 
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
+    <property name="dataSource" ref="{{testClassSimpleName}}"/>
+    <property name="autoStartup" value="false"/>
+    <!-- let Camel start -->
+    <property name="schedulerContextAsMap">
+      <!-- hook Camel into Quartz -->
+      <map>
+        <entry key="CamelQuartzCamelContext-camelContext" 
value-ref="camelContext-{{testClassSimpleName}}"/>
+      </map>
+    </property>
+    <property name="quartzProperties">
+      <props>
+        <prop 
key="org.quartz.scheduler.instanceName">myscheduler-{{runName}}</prop>
+        <prop key="org.quartz.scheduler.instanceId">AUTO</prop>
+        <prop key="org.quartz.scheduler.skipUpdateCheck">true</prop>
+        <prop key="org.terracotta.quartz.skipUpdateCheck">true</prop>
+        <prop 
key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.StdJDBCDelegate</prop>
+        <prop key="org.quartz.jobStore.isClustered">false</prop>
+      </props>
+    </property>
+  </bean>
+
+  <camelContext id="camelContext-{{testClassSimpleName}}" 
managementNamePattern="#name#" xmlns="http://camel.apache.org/schema/spring";>
+    <route id="myRoute">
+      <from 
uri="quartz://app/test?trigger.repeatInterval=1000&amp;trigger.repeatCount=-1&amp;stateful=true&amp;pauseJob=true&amp;deleteJob=false"/>
+      <to uri="log:trigger"/>
+      <to uri="mock:result"/>
+    </route>
+    <route>
+      <from 
uri="quartz://app/kept?trigger.repeatInterval=1000&amp;trigger.repeatCount=-1&amp;stateful=true&amp;deleteJob=false"/>
+      <to uri="log:kept"/>
+      <to uri="mock:kept"/>
+    </route>
+  </camelContext>
+
+</beans>
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 85b82ae6242e..21a2e9379c0f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -4167,3 +4167,14 @@ A new SPI `org.apache.camel.spi.StepHook`, set with 
`ExtendedCamelContext.setSte
 a route. `ErrorHandlerRedeliveryCustomizer` has a new default method 
`useStepHook(String)`, so existing implementations
 need no change. A custom error handler that wants a failure from the hook 
handled like a failure of the step's
 processor (with `onException` and redelivery) can override it.
+
+=== camel-quartz - a route that keeps its trigger fires again when it is added 
again or after a restart
+
+With `deleteJob=false` (as with `pauseJob=true`), a route pauses its trigger 
when it stops, and only the same endpoint
+resumed it. A route that was removed and added again (a reload, a route 
template removed and added), and a route that
+started after a restart with a persistent job store that is not clustered, 
therefore never fired again. The route now
+marks the trigger it pauses, with the key `CamelQuartzTriggerPausedByCamel` in 
the `JobDataMap` of its job, and a
+route resumes a paused trigger with this mark when it starts. A trigger that 
was paused outside Camel (with the Quartz
+API or JMX) has no mark and stays paused; this now also holds when the same 
route is stopped and started again, which
+resumed such a trigger before. A trigger that Camel paused before this version 
has no mark either: resume it once
+with the Quartz API. A clustered job store is not changed, as Camel does not 
pause triggers there.

Reply via email to