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[].