This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch CAMEL-26607-multi-hook
in repository https://gitbox.apache.org/repos/asf/camel.git

commit e4ab519cae0f6820c4b9d191e73b5d4ff890616d
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Nov 7 17:30:49 2026 +0100

    CAMEL-26607: camel-core - getStepHooks and getSendHooks list the installed 
hooks, as getStepListeners does
    
    The getters a step and a send ask stay one hook (the hook itself, or the
    composite), so the hot path is one null check. The new getters list the 
hooks
    in the order they are asked, for tools. The step listener tests of 
CAMEL-26688
    install their hook with addStepHook.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../org/apache/camel/ExtendedCamelContext.java     | 22 +++++++++++++++++++
 .../impl/engine/DefaultCamelContextExtension.java  | 25 ++++++++++++++++++++++
 .../apache/camel/processor/MultipleHooksTest.java  | 25 ++++++++++++++++++++++
 .../apache/camel/processor/StepListenerTest.java   |  2 +-
 .../camel/management/ManagedStepListenerTest.java  |  2 +-
 5 files changed, 74 insertions(+), 2 deletions(-)

diff --git 
a/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java 
b/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java
index 05892874356b..c7c3bd577c07 100644
--- a/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java
+++ b/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java
@@ -531,6 +531,17 @@ public interface ExtendedCamelContext {
         return null;
     }
 
+    /**
+     * Gets the {@link StepHook}s that are installed, in the order they are 
asked (see {@link #addStepHook(StepHook)}),
+     * for the tools that list them; a step asks {@link #getStepHook()}.
+     *
+     * @return the hooks, empty if none is installed (an unmodifiable snapshot)
+     * @since  4.24
+     */
+    default List<StepHook> getStepHooks() {
+        return Collections.emptyList();
+    }
+
     /**
      * Installs a {@link StepHook} that is asked before every processing step 
of a route. More than one hook can be
      * installed: they are asked in their {@link Ordered order} (a hook that 
does not implement {@link Ordered} has
@@ -668,6 +679,17 @@ public interface ExtendedCamelContext {
         return null;
     }
 
+    /**
+     * Gets the {@link SendHook}s that are installed, in the order they run 
(see {@link #addSendHook(SendHook)}), for
+     * the tools that list them; a send asks {@link #getSendHook()}.
+     *
+     * @return the hooks, empty if none is installed (an unmodifiable snapshot)
+     * @since  4.24
+     */
+    default List<SendHook> getSendHooks() {
+        return Collections.emptyList();
+    }
+
     /**
      * Installs a {@link SendHook} that runs around the sends to the producer 
of an endpoint that fire the
      * {@link org.apache.camel.spi.CamelEvent.ExchangeSendingEvent}. More than 
one hook can be installed: they are asked
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelContextExtension.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelContextExtension.java
index 776be4df8db4..25fafec3d447 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelContextExtension.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelContextExtension.java
@@ -90,6 +90,7 @@ import org.apache.camel.support.CompositeSendHook;
 import org.apache.camel.support.CompositeStepHook;
 import org.apache.camel.support.EndpointHelper;
 import org.apache.camel.support.NormalizedUri;
+import org.apache.camel.support.OrderedComparator;
 import org.apache.camel.support.PluginHelper;
 import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.support.startup.DefaultStartupStepRecorder;
@@ -133,6 +134,7 @@ class DefaultCamelContextExtension implements 
ExtendedCamelContext {
     private volatile SendHook sendHook;
     // the installed hooks, in the order they were added (guarded by the lock)
     private final List<SendHook> sendHooks = new ArrayList<>();
+    private volatile List<SendHook> sendHooksInOrder = List.of();
     private volatile CamelContextNameStrategy nameStrategy;
     private volatile ManagementNameStrategy managementNameStrategy;
     private volatile PropertiesComponent propertiesComponent;
@@ -150,6 +152,7 @@ class DefaultCamelContextExtension implements 
ExtendedCamelContext {
     private volatile StepHook stepHook;
     // the installed hooks, in the order they were added (guarded by the lock)
     private final List<StepHook> stepHooks = new ArrayList<>();
+    private volatile List<StepHook> stepHooksInOrder = List.of();
     private volatile TransformerRegistry transformerRegistry;
     private volatile ValidatorRegistry validatorRegistry;
     private volatile TypeConverterRegistry typeConverterRegistry;
@@ -543,6 +546,19 @@ class DefaultCamelContextExtension implements 
ExtendedCamelContext {
         return stepHook;
     }
 
+    @Override
+    public List<StepHook> getStepHooks() {
+        return stepHooksInOrder;
+    }
+
+    /** The hooks in the order they are asked: by their order, and as they 
were added for the same order. */
+    private static <T> List<T> inOrder(List<T> hooks) {
+        List<T> answer = new ArrayList<>(hooks);
+        // a stable sort, as the composite hooks sort
+        answer.sort(OrderedComparator.get());
+        return Collections.unmodifiableList(answer);
+    }
+
     @Override
     public void addStepHook(StepHook stepHook) {
         ObjectHelper.notNull(stepHook, "stepHook");
@@ -551,6 +567,7 @@ class DefaultCamelContextExtension implements 
ExtendedCamelContext {
             if (stepHooks.stream().noneMatch(h -> h == stepHook)) {
                 stepHooks.add(stepHook);
                 this.stepHook = CompositeStepHook.of(stepHooks);
+                this.stepHooksInOrder = inOrder(stepHooks);
             }
         } finally {
             lock.unlock();
@@ -563,6 +580,7 @@ class DefaultCamelContextExtension implements 
ExtendedCamelContext {
         try {
             if (stepHooks.removeIf(h -> h == stepHook)) {
                 this.stepHook = CompositeStepHook.of(stepHooks);
+                this.stepHooksInOrder = inOrder(stepHooks);
             }
         } finally {
             lock.unlock();
@@ -623,6 +641,11 @@ class DefaultCamelContextExtension implements 
ExtendedCamelContext {
         return sendHook;
     }
 
+    @Override
+    public List<SendHook> getSendHooks() {
+        return sendHooksInOrder;
+    }
+
     @Override
     public void addSendHook(SendHook sendHook) {
         ObjectHelper.notNull(sendHook, "sendHook");
@@ -631,6 +654,7 @@ class DefaultCamelContextExtension implements 
ExtendedCamelContext {
             if (sendHooks.stream().noneMatch(h -> h == sendHook)) {
                 sendHooks.add(sendHook);
                 this.sendHook = CompositeSendHook.of(sendHooks);
+                this.sendHooksInOrder = inOrder(sendHooks);
             }
         } finally {
             lock.unlock();
@@ -643,6 +667,7 @@ class DefaultCamelContextExtension implements 
ExtendedCamelContext {
         try {
             if (sendHooks.removeIf(h -> h == sendHook)) {
                 this.sendHook = CompositeSendHook.of(sendHooks);
+                this.sendHooksInOrder = inOrder(sendHooks);
             }
         } finally {
             lock.unlock();
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/MultipleHooksTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/MultipleHooksTest.java
index 999418413b15..e831f38b2650 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/processor/MultipleHooksTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/MultipleHooksTest.java
@@ -24,6 +24,7 @@ import org.apache.camel.CamelContext;
 import org.apache.camel.ContextTestSupport;
 import org.apache.camel.Endpoint;
 import org.apache.camel.Exchange;
+import org.apache.camel.ExtendedCamelContext;
 import org.apache.camel.Ordered;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.spi.SendHook;
@@ -31,6 +32,10 @@ import org.apache.camel.spi.StepHook;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
  * Two step hooks and two send hooks active together (CAMEL-26607): a hook 
that fails the first attempt, as chaos does,
@@ -89,6 +94,26 @@ class MultipleHooksTest extends ContextTestSupport {
                 recorded);
     }
 
+    @Test
+    void testTheHooksAreListedInTheirOrder() {
+        ExtendedCamelContext ecc = context.getCamelContextExtension();
+        // added failing first, listed last: as they are asked
+        assertEquals(List.of(RecordingStepHook.class, FailingStepHook.class),
+                ecc.getStepHooks().stream().map(Object::getClass).toList());
+        assertEquals(List.of(RecordingSendHook.class, FailingSendHook.class),
+                ecc.getSendHooks().stream().map(Object::getClass).toList());
+        assertThrows(UnsupportedOperationException.class, () -> 
ecc.getStepHooks().clear());
+
+        StepHook failing = ecc.getStepHooks().get(1);
+        ecc.removeStepHook(failing);
+        assertEquals(List.of(RecordingStepHook.class), 
ecc.getStepHooks().stream().map(Object::getClass).toList());
+        // one hook left: the step asks it, not a composite of one
+        assertSame(ecc.getStepHooks().get(0), ecc.getStepHook());
+        ecc.removeStepHook(ecc.getStepHooks().get(0));
+        assertTrue(ecc.getStepHooks().isEmpty());
+        assertNull(ecc.getStepHook());
+    }
+
     private final class RecordingStepHook implements StepHook {
 
         @Override
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/StepListenerTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/StepListenerTest.java
index a9f089bc1cf0..f1ccd5d1e149 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/processor/StepListenerTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/StepListenerTest.java
@@ -78,7 +78,7 @@ class StepListenerTest extends ContextTestSupport {
         // the failing listener first, so its failures come before the 
recorder is called
         context.getCamelContextExtension().addStepListener(failing);
         context.getCamelContextExtension().addStepListener(recorder);
-        context.getCamelContextExtension().setStepHook(this::beforeStep);
+        context.getCamelContextExtension().addStepHook(this::beforeStep);
         return context;
     }
 
diff --git 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedStepListenerTest.java
 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedStepListenerTest.java
index 1dcbf901c992..b6b14e28bde1 100644
--- 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedStepListenerTest.java
+++ 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedStepListenerTest.java
@@ -77,7 +77,7 @@ class ManagedStepListenerTest extends ManagementTestSupport {
                 events.add("callback " + state);
             }
         });
-        context.getCamelContextExtension().setStepHook(this::beforeStep);
+        context.getCamelContextExtension().addStepHook(this::beforeStep);
         return context;
     }
 

Reply via email to