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 10f69b4430ee CAMEL-25515: camel-jgroups-raft - the consumer handles 
the Learner role (#27870)
10f69b4430ee is described below

commit 10f69b4430eeb184b5fb4c4bb3ac8b5c23478ba2
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Oct 15 14:00:48 2026 +0200

    CAMEL-25515: camel-jgroups-raft - the consumer handles the Learner role 
(#27870)
    
    Co-Authored-By: Claude Opus 4.8 <[email protected]>
---
 .../camel/catalog/docs/jgroups-raft-component.adoc |  4 ++
 .../src/main/docs/jgroups-raft-component.adoc      |  4 ++
 .../jgroups/raft/CamelRoleChangeListener.java      | 30 ++++++++++----
 .../jgroups/raft/CamelRoleChangeListenerTest.java  | 46 ++++++++++++++++++++++
 4 files changed, 77 insertions(+), 7 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jgroups-raft-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jgroups-raft-component.adoc
index f10c5a39eb24..3a9893ca4ce7 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jgroups-raft-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jgroups-raft-component.adoc
@@ -164,6 +164,10 @@ YAML::
 ----
 ====
 
+The role change is forwarded as a `LEADER` or `FOLLOWER` event. A `Learner` (a 
non-voting member that replicates the
+log but cannot vote or become leader) is reported as a `FOLLOWER` event, so a 
route may receive consecutive `FOLLOWER`
+events, for example when a node steps down from leader to learner and again 
when it becomes a voting follower.
+
 === Keeping singleton route within the cluster
 
 The snippet below demonstrates how to keep the singleton consumer route
diff --git 
a/components/camel-jgroups-raft/src/main/docs/jgroups-raft-component.adoc 
b/components/camel-jgroups-raft/src/main/docs/jgroups-raft-component.adoc
index f10c5a39eb24..3a9893ca4ce7 100644
--- a/components/camel-jgroups-raft/src/main/docs/jgroups-raft-component.adoc
+++ b/components/camel-jgroups-raft/src/main/docs/jgroups-raft-component.adoc
@@ -164,6 +164,10 @@ YAML::
 ----
 ====
 
+The role change is forwarded as a `LEADER` or `FOLLOWER` event. A `Learner` (a 
non-voting member that replicates the
+log but cannot vote or become leader) is reported as a `FOLLOWER` event, so a 
route may receive consecutive `FOLLOWER`
+events, for example when a node steps down from leader to learner and again 
when it becomes a voting follower.
+
 === Keeping singleton route within the cluster
 
 The snippet below demonstrates how to keep the singleton consumer route
diff --git 
a/components/camel-jgroups-raft/src/main/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListener.java
 
b/components/camel-jgroups-raft/src/main/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListener.java
index a5d143d88d1b..04bc47315708 100644
--- 
a/components/camel-jgroups-raft/src/main/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListener.java
+++ 
b/components/camel-jgroups-raft/src/main/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListener.java
@@ -42,18 +42,34 @@ public class CamelRoleChangeListener implements 
RAFT.RoleChange {
     @Override
     public void roleChanged(Role role) {
         LOG.trace("New Role {} received.", role);
+        JGroupsRaftEventType eventType = toEventType(role);
+        if (eventType == null) {
+            // Guard against future jgroups-raft Role values: log and ignore 
rather than throw. RAFT swallows an
+            // exception thrown from this callback, so throwing would leave 
the consumer silently without an event.
+            LOG.warn("Ignoring jgroups-raft role change to unhandled role 
[{}]", role);
+            return;
+        }
         Exchange exchange = createExchange();
+        
exchange.getIn().setHeader(JGroupsRaftConstants.HEADER_JGROUPSRAFT_EVENT_TYPE, 
eventType);
+        processExchange(role, exchange);
+    }
+
+    /**
+     * Maps a jgroups-raft {@link Role} to the {@link JGroupsRaftEventType} 
dispatched to the route, or {@code null} for
+     * a role that produces no event. A {@code Learner} is a non-voting 
follower (it replicates the log but cannot vote
+     * or become leader), so it maps to {@link JGroupsRaftEventType#FOLLOWER}: 
a route that stops its leader work on a
+     * {@code FOLLOWER} event then also stops it when this node steps down to 
{@code Learner} (CAMEL-25515). This
+     * mirrors the cluster-side handling added for CAMEL-25488.
+     */
+    static JGroupsRaftEventType toEventType(Role role) {
         switch (role) {
             case Leader:
-                
exchange.getIn().setHeader(JGroupsRaftConstants.HEADER_JGROUPSRAFT_EVENT_TYPE, 
JGroupsRaftEventType.LEADER);
-                processExchange(role, exchange);
-                break;
+                return JGroupsRaftEventType.LEADER;
             case Follower:
-                
exchange.getIn().setHeader(JGroupsRaftConstants.HEADER_JGROUPSRAFT_EVENT_TYPE, 
JGroupsRaftEventType.FOLLOWER);
-                processExchange(role, exchange);
-                break;
+            case Learner:
+                return JGroupsRaftEventType.FOLLOWER;
             default:
-                throw new JGroupsRaftException("Role [" + role + "] unknown.");
+                return null;
         }
     }
 
diff --git 
a/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListenerTest.java
 
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListenerTest.java
new file mode 100644
index 000000000000..27f19b1c85a1
--- /dev/null
+++ 
b/components/camel-jgroups-raft/src/test/java/org/apache/camel/component/jgroups/raft/CamelRoleChangeListenerTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.jgroups.raft;
+
+import org.jgroups.protocols.raft.Role;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Unit tests for {@link CamelRoleChangeListener#toEventType(Role)} 
(CAMEL-25515).
+ */
+class CamelRoleChangeListenerTest {
+
+    @Test
+    void leaderMapsToLeaderEvent() {
+        assertEquals(JGroupsRaftEventType.LEADER, 
CamelRoleChangeListener.toEventType(Role.Leader));
+    }
+
+    @Test
+    void followerMapsToFollowerEvent() {
+        assertEquals(JGroupsRaftEventType.FOLLOWER, 
CamelRoleChangeListener.toEventType(Role.Follower));
+    }
+
+    @Test
+    void learnerMapsToFollowerEvent() {
+        // CAMEL-25515: a learner is a non-voting follower; it must produce a 
FOLLOWER event (previously it hit the
+        // default branch and threw, which RAFT swallowed, so the consumer got 
no event and a route that steps down
+        // on FOLLOWER kept acting as leader after the node was moved to 
Learner).
+        assertEquals(JGroupsRaftEventType.FOLLOWER, 
CamelRoleChangeListener.toEventType(Role.Learner));
+    }
+}

Reply via email to