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 8a3c668f913c CAMEL-23302: camel-xslt - Auto-disable content cache in 
routes-reload (dev) mode (#23127)
8a3c668f913c is described below

commit 8a3c668f913cb96fbe0a3572ad807f68b9a8b05e
Author: Adriano Machado <[email protected]>
AuthorDate: Wed May 13 01:40:57 2026 -0400

    CAMEL-23302: camel-xslt - Auto-disable content cache in routes-reload (dev) 
mode (#23127)
    
    * CAMEL-23302: camel-xslt - Auto-disable content cache in routes-reload 
(dev) mode
    
    Introduce `ContentCacheAware` SPI so resource-based components can opt into
    having their content cache set to false when 
`camel.main.routesReloadEnabled`
    is set to `true` (set automatically by `camel run --dev`). A new
    `DevModeContentCacheStrategy` applies the setting on component registration,
    and explicit user settings on the component, endpoint or URI are always 
respected.
    
    `camel-xslt` is the first adopter: `XsltComponent` now implements 
`ContentCacheAware`
    and stores `contentCache` as a Boolean (null = unset). Other resource-based
    components can be migrated in a follow-up.
    
    Co-Authored-By: Claude Opus 4.7 <[email protected]>
    
    rh-pre-commit.version: 2.3.2
    rh-pre-commit.check-secrets: ENABLED
    
    * CAMEL-23302: adding missing generated files
    
    rh-pre-commit.version: 2.3.2
    rh-pre-commit.check-secrets: ENABLED
    
    * CAMEL-23302: Make `DevModeContentCacheStrategy` package-private 
visibility and log level.
    
    rh-pre-commit.version: 2.3.2
    rh-pre-commit.check-secrets: ENABLED
    
    * CAMEL-23302: Moving implementation from `ContentCacheAware` SPI to 
`PropertyConfigurer`
    
    rh-pre-commit.version: 2.3.2
    rh-pre-commit.check-secrets: ENABLED
---
 .../camel-xslt/src/main/docs/xslt-component.adoc   |   8 +
 .../camel/main/DefaultConfigurationConfigurer.java |   3 +
 .../camel/main/DevModeContentCacheStrategy.java    |  53 ++++++
 .../camel/main/MainDevModeContentCacheTest.java    | 204 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_21.adoc    |   6 +
 .../modules/ROOT/pages/camel-jbang.adoc            |  10 +
 .../modules/ROOT/pages/route-reload.adoc           |  10 +
 7 files changed, 294 insertions(+)

diff --git a/components/camel-xslt/src/main/docs/xslt-component.adoc 
b/components/camel-xslt/src/main/docs/xslt-component.adoc
index 85cfc8f6f17b..18bbad4fea25 100644
--- a/components/camel-xslt/src/main/docs/xslt-component.adoc
+++ b/components/camel-xslt/src/main/docs/xslt-component.adoc
@@ -185,6 +185,14 @@ TIP: You can set `contentCache=false` and refer to a 
non-existing template, such
 as this will tell Camel to not load `dummy.xsl` on startup but to load the 
stylesheet on demand. And because you
 provide the stylesheet via headers, then it is fully dynamic.
 
+== Live reload in dev mode
+
+When routes-reload is enabled (`camel.main.routesReloadEnabled=true`, 
automatically set by `camel run --dev`),
+Camel disables `contentCache` on the XSLT component so that edits to the 
stylesheet file are picked up
+on the next message without having to restart the route. User properties (e.g.
+`camel.component.xslt.contentCache=true`) and explicit endpoint/URI settings 
are always respected:
+set `contentCache=true` to keep caching even in dev mode.
+
 == Accessing warnings, errors and fatalErrors from XSLT ErrorListener
 
 Any warning/error or fatalError is stored on
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
index a7b580fe897a..84793d6100f3 100644
--- 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
@@ -298,6 +298,9 @@ public final class DefaultConfigurationConfigurer {
             reloader.setPattern(config.getRoutesReloadPattern());
             
reloader.setRemoveAllRoutes(config.isRoutesReloadRemoveAllRoutes());
             camelContext.addService(reloader);
+            // disable contentCache on resource-based components so that 
resource files (e.g. XSLT
+            // stylesheets, templates) are reloaded live without restarting 
routes
+            camelContext.addLifecycleStrategy(new 
DevModeContentCacheStrategy());
         }
         if (config.getDumpRoutes() != null) {
             DumpRoutesStrategy drs = 
camelContext.getCamelContextExtension().getContextPlugin(DumpRoutesStrategy.class);
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/DevModeContentCacheStrategy.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/DevModeContentCacheStrategy.java
new file mode 100644
index 000000000000..8dc4ebde5fb9
--- /dev/null
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/DevModeContentCacheStrategy.java
@@ -0,0 +1,53 @@
+/*
+ * 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.main;
+
+import org.apache.camel.Component;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.support.LifecycleStrategySupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Lifecycle strategy that disables {@code contentCache} on resource-based 
components when routes-reload is enabled, so
+ * users can edit a resource file (e.g. an XSLT stylesheet, FreeMarker 
template) and see the change applied live.
+ *
+ * Components are detected via their generated {@link PropertyConfigurer}: any 
component whose configurer exposes a
+ * {@code contentCache} option currently evaluating to {@link Boolean#TRUE} is 
flipped to {@code false}. User properties
+ * applied later (e.g. {@code camel.component.<name>.contentCache=true}) will 
override this default.
+ */
+class DevModeContentCacheStrategy extends LifecycleStrategySupport {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(DevModeContentCacheStrategy.class);
+
+    private static final String CONTENT_CACHE = "contentCache";
+
+    @Override
+    public void onComponentAdd(String name, Component component) {
+        PropertyConfigurer configurer = 
component.getComponentPropertyConfigurer();
+        if (!(configurer instanceof PropertyConfigurerGetter getter)) {
+            return;
+        }
+        Object value = getter.getOptionValue(component, CONTENT_CACHE, true);
+        if (Boolean.TRUE.equals(value)
+                && configurer.configure(component.getCamelContext(), 
component, CONTENT_CACHE, Boolean.FALSE, true)) {
+            LOG.debug("Routes-reload is enabled: disabling contentCache on 
component '{}' for live resource reload",
+                    name);
+        }
+    }
+}
diff --git 
a/core/camel-main/src/test/java/org/apache/camel/main/MainDevModeContentCacheTest.java
 
b/core/camel-main/src/test/java/org/apache/camel/main/MainDevModeContentCacheTest.java
new file mode 100644
index 000000000000..dd97a841fccc
--- /dev/null
+++ 
b/core/camel-main/src/test/java/org/apache/camel/main/MainDevModeContentCacheTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.main;
+
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Consumer;
+import org.apache.camel.Endpoint;
+import org.apache.camel.Processor;
+import org.apache.camel.Producer;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.support.DefaultComponent;
+import org.apache.camel.support.DefaultEndpoint;
+import org.apache.camel.support.component.PropertyConfigurerSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class MainDevModeContentCacheTest {
+
+    @Test
+    public void shouldDisableContentCacheWhenRoutesReloadEnabled() {
+        Main main = new Main();
+        main.configure().withRoutesReloadEnabled(true);
+        main.bind("dummy", new TestContentCacheComponent());
+
+        main.start();
+        try {
+            TestContentCacheComponent c = 
main.getCamelContext().getComponent("dummy", TestContentCacheComponent.class);
+            assertFalse(c.isContentCache(),
+                    "contentCache should be auto-disabled when 
routesReloadEnabled=true");
+        } finally {
+            main.stop();
+        }
+    }
+
+    @Test
+    public void shouldKeepContentCacheEnabledWhenRoutesReloadDisabled() {
+        Main main = new Main();
+        // routesReloadEnabled defaults to false
+        main.bind("dummy", new TestContentCacheComponent());
+
+        main.start();
+        try {
+            TestContentCacheComponent c = 
main.getCamelContext().getComponent("dummy", TestContentCacheComponent.class);
+            assertTrue(c.isContentCache(),
+                    "contentCache should remain at its default (true) when 
routesReloadEnabled is not active");
+        } finally {
+            main.stop();
+        }
+    }
+
+    @Test
+    public void shouldRespectExplicitContentCacheFalse() {
+        Main main = new Main();
+        main.configure().withRoutesReloadEnabled(true);
+        TestContentCacheComponent component = new TestContentCacheComponent();
+        component.setContentCache(false);
+        main.bind("dummy", component);
+
+        main.start();
+        try {
+            TestContentCacheComponent c = 
main.getCamelContext().getComponent("dummy", TestContentCacheComponent.class);
+            assertFalse(c.isContentCache(),
+                    "explicit user setting (false) must not be touched by 
dev-mode auto-flip");
+        } finally {
+            main.stop();
+        }
+    }
+
+    @Test
+    public void shouldHonorMainPropertyOverride() {
+        Main main = new Main();
+        main.configure().withRoutesReloadEnabled(true);
+        main.addInitialProperty("camel.component.dummy.contentCache", "true");
+        main.bind("dummy", new TestContentCacheComponent());
+
+        main.start();
+        try {
+            TestContentCacheComponent c = 
main.getCamelContext().getComponent("dummy", TestContentCacheComponent.class);
+            assertTrue(c.isContentCache(),
+                    "camel.component.<name>.contentCache=true must override 
the dev-mode auto-flip");
+        } finally {
+            main.stop();
+        }
+    }
+
+    @Test
+    public void shouldRespectExplicitContentCacheOnUri() {
+        Main main = new Main();
+        main.configure().withRoutesReloadEnabled(true);
+        main.bind("dummy", new TestContentCacheComponent());
+
+        main.start();
+        try {
+            TestContentCacheComponent c = 
main.getCamelContext().getComponent("dummy", TestContentCacheComponent.class);
+            assertFalse(c.isContentCache(), "component-level contentCache 
should be auto-disabled");
+
+            TestContentCacheEndpoint endpoint
+                    = (TestContentCacheEndpoint) 
main.getCamelContext().getEndpoint("dummy:foo?contentCache=true");
+            assertTrue(endpoint.isContentCache(),
+                    "explicit contentCache=true on URI must override the 
component-level auto-flip");
+        } finally {
+            main.stop();
+        }
+    }
+
+    static final class TestContentCacheComponent extends DefaultComponent {
+
+        private boolean contentCache = true;
+
+        public boolean isContentCache() {
+            return contentCache;
+        }
+
+        public void setContentCache(boolean contentCache) {
+            this.contentCache = contentCache;
+        }
+
+        @Override
+        public PropertyConfigurer getComponentPropertyConfigurer() {
+            return new TestContentCacheComponentConfigurer();
+        }
+
+        @Override
+        protected Endpoint createEndpoint(String uri, String remaining, 
Map<String, Object> parameters) throws Exception {
+            TestContentCacheEndpoint endpoint = new 
TestContentCacheEndpoint(uri, this);
+            endpoint.setContentCache(contentCache);
+            setProperties(endpoint, parameters);
+            return endpoint;
+        }
+    }
+
+    static final class TestContentCacheComponentConfigurer
+            extends PropertyConfigurerSupport
+            implements PropertyConfigurer, PropertyConfigurerGetter {
+
+        @Override
+        public boolean configure(CamelContext camelContext, Object target, 
String name, Object value, boolean ignoreCase) {
+            if ("contentcache".equalsIgnoreCase(name)) {
+                ((TestContentCacheComponent) 
target).setContentCache(property(camelContext, boolean.class, value));
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public Object getOptionValue(Object target, String name, boolean 
ignoreCase) {
+            if ("contentcache".equalsIgnoreCase(name)) {
+                return ((TestContentCacheComponent) target).isContentCache();
+            }
+            return null;
+        }
+
+        @Override
+        public Class<?> getOptionType(String name, boolean ignoreCase) {
+            return "contentcache".equalsIgnoreCase(name) ? boolean.class : 
null;
+        }
+    }
+
+    static final class TestContentCacheEndpoint extends DefaultEndpoint {
+
+        private boolean contentCache;
+
+        TestContentCacheEndpoint(String uri, TestContentCacheComponent 
component) {
+            super(uri, component);
+        }
+
+        public boolean isContentCache() {
+            return contentCache;
+        }
+
+        public void setContentCache(boolean contentCache) {
+            this.contentCache = contentCache;
+        }
+
+        @Override
+        public Producer createProducer() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Consumer createConsumer(Processor processor) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
index d1e3ccb2cc16..ffd96ad7aad7 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
@@ -46,6 +46,12 @@ and dev consoles for nodes inside Choice EIP branches.
 The `camel wrapper` command now installs the scripts as `camel` instead of 
`camelw`.
 You can use the `--command-name=camelw` to use the old name.
 
+When `camel.main.routesReloadEnabled=true` (automatically set by `camel run 
--dev`), Camel now
+auto-disables `contentCache` on resource-based components (such as `xslt`) 
whose default is
+`true`, so that edits to the resource file are picked up on the next message 
without restarting
+the route. Set `camel.component.<name>.contentCache=true` (or pass 
`?contentCache=true` on the
+URI) to opt back in to caching during dev mode.
+
 === camel-yaml-dsl
 
 A new canonical JSON Schema variant (`camelYamlDsl-canonical.json`) has been 
added alongside the existing classic
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
index de578f12c467..0fc137256bcc 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
@@ -529,6 +529,16 @@ and reloaded. You can also delete files to remove routes.
 NOTE: You cannot use both files and source dir together.
 The following is not allowed: `camel run abc.java --source-dir=mycode`.
 
+==== Live reload of resource files
+
+When `--dev` is used, Camel also disables `contentCache` on resource-based 
components (such as
+`xslt`) whose default is `true`, so that edits to resource files are picked up 
on the next message
+without having to restart the route.
+
+This is driven by `camel.main.routesReloadEnabled` (set automatically by 
`--dev`). User properties
+(e.g. `camel.component.xslt.contentCache=true`) and explicit endpoint/URI 
settings are always
+respected: set `contentCache=true` to keep caching even in dev mode.
+
 ==== Loading new routes into existing Camel
 
 *Available as of Camel 4.17*
diff --git a/docs/user-manual/modules/ROOT/pages/route-reload.adoc 
b/docs/user-manual/modules/ROOT/pages/route-reload.adoc
index 03f2c7e745dd..ad80593a7b25 100644
--- a/docs/user-manual/modules/ROOT/pages/route-reload.adoc
+++ b/docs/user-manual/modules/ROOT/pages/route-reload.adoc
@@ -70,6 +70,16 @@ This is necessary because Apache Camel must stop the 
existing routes from runnin
 
 And adding new routes is therefore possible as they would have a new unique 
route id specified.
 
+=== Live reload of resource files
+
+In addition to reloading routes, when `routesReloadEnabled=true` Camel also 
disables the
+`contentCache` option on resource-based components (such as `xslt`) whose 
default is `true`. This
+way, edits to resource files are picked up on the next message without having 
to restart the
+route.
+
+User properties (e.g. `camel.component.xslt.contentCache=true`) and explicit 
endpoint/URI settings
+are always respected: set `contentCache=true` to keep caching even when route 
reload is enabled.
+
 == See Also
 
 See related xref:context-reload.adoc[].

Reply via email to