This is an automated email from the ASF dual-hosted git repository. fmariani pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit c28e0d715645a0e09b044cd97cdbba14351eb890 Author: Croway <[email protected]> AuthorDate: Tue Apr 7 12:12:53 2026 +0200 CAMEL-23252: Add onReload hook to ContextServicePlugin for dev-mode route reloading --- .../spi/ContextServiceLoaderPluginResolver.java | 6 + .../org/apache/camel/spi/ContextServicePlugin.java | 16 ++ .../engine/DefaultContextServiceLoaderPlugin.java | 15 ++ .../RouteWatcherReloadStrategyOnReloadTest.java | 169 +++++++++++++++++++++ .../camel/support/RouteWatcherReloadStrategy.java | 8 + 5 files changed, 214 insertions(+) diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/ContextServiceLoaderPluginResolver.java b/core/camel-api/src/main/java/org/apache/camel/spi/ContextServiceLoaderPluginResolver.java index 4e49e02cc920..0262342e1c9d 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/ContextServiceLoaderPluginResolver.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/ContextServiceLoaderPluginResolver.java @@ -38,4 +38,10 @@ import org.apache.camel.StatefulService; * @see StatefulService */ public interface ContextServiceLoaderPluginResolver extends CamelContextAware, StatefulService { + + /** + * Invokes {@link ContextServicePlugin#onReload(org.apache.camel.CamelContext)} on all discovered plugins, giving + * them an opportunity to refresh their state before routes are reloaded. + */ + void onReload(); } diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/ContextServicePlugin.java b/core/camel-api/src/main/java/org/apache/camel/spi/ContextServicePlugin.java index 3dc07b7c2268..09e6d3133360 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/ContextServicePlugin.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/ContextServicePlugin.java @@ -67,6 +67,22 @@ public interface ContextServicePlugin { */ void load(CamelContext camelContext); + /** + * Called before route reloading in development mode to allow the plugin to refresh its state. + * <p> + * This method is invoked by the route watcher reload strategy before routes are reloaded, giving plugins the + * opportunity to refresh bean references, re-read configuration, or perform other updates that should happen + * before the new routes are started. + * <p> + * The default implementation does nothing. Plugins that register beans or other resources that may become stale + * when properties change should override this method to refresh those resources. + * + * @param camelContext the CamelContext being reloaded, never {@code null} + */ + default void onReload(CamelContext camelContext) { + // NO-OP + } + /** * Called during CamelContext stop. Use it to free allocated resources. * diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultContextServiceLoaderPlugin.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultContextServiceLoaderPlugin.java index 8104a43bf260..d964d9fa784c 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultContextServiceLoaderPlugin.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultContextServiceLoaderPlugin.java @@ -76,6 +76,21 @@ public class DefaultContextServiceLoaderPlugin extends ServiceSupport implements } } + @Override + public void onReload() { + if (contextServicePlugins != null) { + for (ContextServicePlugin plugin : contextServicePlugins) { + try { + plugin.onReload(camelContext); + } catch (Exception e) { + LOG.warn( + "Reloading of plugin {} failed, however the exception will be ignored so other plugins can be reloaded. Reason: {}", + plugin.getClass().getName(), e.getMessage(), e); + } + } + } + } + @Override protected void doStop() throws Exception { if (contextServicePlugins != null) { diff --git a/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadStrategyOnReloadTest.java b/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadStrategyOnReloadTest.java new file mode 100644 index 000000000000..680e4a27b9b5 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadStrategyOnReloadTest.java @@ -0,0 +1,169 @@ +/* + * 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.support; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.camel.CamelContext; +import org.apache.camel.ContextTestSupport; +import org.apache.camel.ServiceStatus; +import org.apache.camel.spi.ContextServiceLoaderPluginResolver; +import org.apache.camel.spi.ContextServicePlugin; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class RouteWatcherReloadStrategyOnReloadTest extends ContextTestSupport { + + @Test + public void testOnReloadInvokesPlugins() throws Exception { + AtomicInteger reloadCount = new AtomicInteger(); + + // Create a test plugin that tracks reload calls + ContextServicePlugin testPlugin = new ContextServicePlugin() { + @Override + public void load(CamelContext camelContext) { + // NO-OP + } + + @Override + public void onReload(CamelContext camelContext) { + reloadCount.incrementAndGet(); + } + }; + + // Register a custom resolver with our test plugin + ContextServiceLoaderPluginResolver resolver = new TestContextServiceLoaderPluginResolver(context, testPlugin); + context.getCamelContextExtension().addContextPlugin(ContextServiceLoaderPluginResolver.class, resolver); + + RouteWatcherReloadStrategy strategy = new RouteWatcherReloadStrategy(); + strategy.setCamelContext(context); + + // Trigger route reload + strategy.onRouteReload(Collections.emptyList(), false); + + assertEquals(1, reloadCount.get(), "onReload should have been called once on the plugin"); + + // Trigger another reload + strategy.onRouteReload(Collections.emptyList(), false); + + assertEquals(2, reloadCount.get(), "onReload should have been called twice on the plugin"); + } + + @Test + public void testOnReloadDefaultNoOp() throws Exception { + // Verify the default onReload is a no-op and does not throw + ContextServicePlugin plugin = new ContextServicePlugin() { + @Override + public void load(CamelContext camelContext) { + // NO-OP + } + }; + + // Should not throw + plugin.onReload(context); + } + + /** + * A simple test resolver that delegates to a single plugin. + */ + private static class TestContextServiceLoaderPluginResolver implements ContextServiceLoaderPluginResolver { + private CamelContext camelContext; + private final ContextServicePlugin plugin; + + TestContextServiceLoaderPluginResolver(CamelContext camelContext, ContextServicePlugin plugin) { + this.camelContext = camelContext; + this.plugin = plugin; + } + + @Override + public void onReload() { + plugin.onReload(camelContext); + } + + @Override + public void setCamelContext(CamelContext camelContext) { + this.camelContext = camelContext; + } + + @Override + public CamelContext getCamelContext() { + return camelContext; + } + + @Override + public void start() { + } + + @Override + public void stop() { + } + + @Override + public void suspend() { + } + + @Override + public void resume() { + } + + @Override + public void shutdown() { + } + + @Override + public ServiceStatus getStatus() { + return ServiceStatus.Started; + } + + @Override + public boolean isStarted() { + return true; + } + + @Override + public boolean isStarting() { + return false; + } + + @Override + public boolean isStopping() { + return false; + } + + @Override + public boolean isStopped() { + return false; + } + + @Override + public boolean isSuspending() { + return false; + } + + @Override + public boolean isSuspended() { + return false; + } + + @Override + public boolean isRunAllowed() { + return true; + } + } +} diff --git a/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java b/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java index 77791c589521..b03ee1a84ff2 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java @@ -33,6 +33,7 @@ import org.apache.camel.RuntimeCamelException; import org.apache.camel.ServiceStatus; import org.apache.camel.StartupSummaryLevel; import org.apache.camel.spi.GroovyScriptCompiler; +import org.apache.camel.spi.ContextServiceLoaderPluginResolver; import org.apache.camel.spi.PropertiesComponent; import org.apache.camel.spi.PropertiesReload; import org.apache.camel.spi.PropertiesSource; @@ -247,6 +248,13 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg @SuppressWarnings("unchecked") protected void onRouteReload(Collection<Resource> resources, boolean removeEverything) { + // notify context service plugins before reloading routes + ContextServiceLoaderPluginResolver pluginResolver + = getCamelContext().getCamelContextExtension().getContextPlugin(ContextServiceLoaderPluginResolver.class); + if (pluginResolver != null) { + pluginResolver.onReload(); + } + // remember all existing resources List<Resource> sources = new ArrayList<>();
