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 a6bc7c254143 CAMEL-24718: observability must not report a
CamelSqlQuery header the endpoint ignored
a6bc7c254143 is described below
commit a6bc7c254143b83476bed5491c93cfac7c99c945
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Sep 21 21:06:03 2026 +0200
CAMEL-24718: observability must not report a CamelSqlQuery header the
endpoint ignored
CAMEL-24292 gated the CamelSqlQuery header behind allowQueryFromHeader,
which is off by default, so SqlProducer ignores the header and runs the
endpoint-configured query. Three observability sites still read the
header unconditionally - the SqlSpanDecorator in camel-telemetry and
camel-tracing, and SqlTraceDevConsole - and so attributed a statement
that was never executed, with sender-controlled text, to the exchange.
Each site now consults the endpoint's allowQueryFromHeader through the
generated PropertyConfigurer (no camel-sql dependency, no per-exchange
factory lookups) and falls back to the endpoint query. An endpoint that
does not declare the option, such as jdbc, never honours the header.
Tests cover both polarities in all three modules; the 4.23 upgrade
guide notes the observability consequence. No backport: the option
only exists from 4.23.0.
Closes #26689
Co-Authored-By: Claude Opus 5 <[email protected]>
---
.../telemetry/decorators/SqlQueryHeaderHelper.java | 57 ++++++++
.../telemetry/decorators/SqlSpanDecorator.java | 12 +-
.../telemetry/decorators/SqlSpanDecoratorTest.java | 83 +++++++++++-
.../tracing/decorators/SqlQueryHeaderHelper.java | 57 ++++++++
.../camel/tracing/decorators/SqlSpanDecorator.java | 12 +-
.../tracing/decorators/SqlSpanDecoratorTest.java | 83 +++++++++++-
.../camel/impl/console/SqlTraceDevConsole.java | 39 +++++-
.../console/SqlTraceDevConsoleQueryHeaderTest.java | 146 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 7 +
9 files changed, 474 insertions(+), 22 deletions(-)
diff --git
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlQueryHeaderHelper.java
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlQueryHeaderHelper.java
new file mode 100644
index 000000000000..0e489a5f78ee
--- /dev/null
+++
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlQueryHeaderHelper.java
@@ -0,0 +1,57 @@
+/*
+ * 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.telemetry.decorators;
+
+import org.apache.camel.Component;
+import org.apache.camel.Endpoint;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.support.DefaultEndpoint;
+
+/**
+ * Determines whether an endpoint honours the {@code CamelSqlQuery} header.
+ * <p/>
+ * The camel-sql components gate that header behind the {@code
allowQueryFromHeader} option, which is disabled by
+ * default. When it is disabled the header is ignored and the
endpoint-configured query is executed instead, so
+ * surfacing the header value would attribute a statement to the exchange that
never ran, and would place
+ * sender-controlled text into telemetry.
+ * <p/>
+ * The option is read through the endpoint's generated {@link
PropertyConfigurer} rather than by casting, because the
+ * tracing modules must not depend on camel-sql. An endpoint that does not
declare the option at all (jdbc, for
+ * instance) never honours the header.
+ */
+final class SqlQueryHeaderHelper {
+
+ private static final String ALLOW_QUERY_FROM_HEADER =
"allowQueryFromHeader";
+
+ private SqlQueryHeaderHelper() {
+ }
+
+ static boolean isQueryHeaderHonoured(Endpoint endpoint) {
+ if (!(endpoint instanceof DefaultEndpoint defaultEndpoint)) {
+ return false;
+ }
+ Component component = defaultEndpoint.getComponent();
+ if (component == null) {
+ return false;
+ }
+ if (component.getEndpointPropertyConfigurer() instanceof
PropertyConfigurerGetter getter) {
+ return Boolean.TRUE.equals(getter.getOptionValue(endpoint,
ALLOW_QUERY_FROM_HEADER, true));
+ }
+ return false;
+ }
+}
diff --git
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlSpanDecorator.java
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlSpanDecorator.java
index e50aaa477e06..945f8ec81730 100644
---
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlSpanDecorator.java
+++
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlSpanDecorator.java
@@ -21,6 +21,8 @@ import org.apache.camel.Exchange;
import org.apache.camel.telemetry.Span;
import org.apache.camel.telemetry.TagConstants;
+import static
org.apache.camel.telemetry.decorators.SqlQueryHeaderHelper.isQueryHeaderHonoured;
+
public class SqlSpanDecorator extends AbstractSpanDecorator {
public static final String CAMEL_SQL_QUERY = "CamelSqlQuery";
@@ -40,9 +42,13 @@ public class SqlSpanDecorator extends AbstractSpanDecorator {
super.beforeTracingEvent(span, exchange, endpoint);
span.setTag(TagConstants.DB_SYSTEM, "sql");
- String query = exchange.getIn().getHeader(CAMEL_SQL_QUERY,
String.class);
- if (query != null) {
- span.setTag(TagConstants.DB_STATEMENT, query);
+ // the header only reaches the database when the endpoint opts in, so
tagging it
+ // unconditionally would report a statement that was never executed
+ if (isQueryHeaderHonoured(endpoint)) {
+ String query = exchange.getIn().getHeader(CAMEL_SQL_QUERY,
String.class);
+ if (query != null) {
+ span.setTag(TagConstants.DB_STATEMENT, query);
+ }
}
}
diff --git
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/decorators/SqlSpanDecoratorTest.java
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/decorators/SqlSpanDecoratorTest.java
index fae04200536c..bcc08664f91d 100644
---
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/decorators/SqlSpanDecoratorTest.java
+++
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/decorators/SqlSpanDecoratorTest.java
@@ -16,9 +16,14 @@
*/
package org.apache.camel.telemetry.decorators;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Component;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.support.DefaultEndpoint;
import org.apache.camel.telemetry.SpanDecorator;
import org.apache.camel.telemetry.TagConstants;
import org.apache.camel.telemetry.mock.MockSpanAdapter;
@@ -26,14 +31,49 @@ import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
public class SqlSpanDecoratorTest {
private static final String SQL_STATEMENT = "select * from customer";
@Test
- public void testPre() {
- Endpoint endpoint = Mockito.mock(Endpoint.class);
+ public void testPreTagsTheQueryHeaderWhenTheEndpointAllowsIt() {
+ MockSpanAdapter span = decorate(endpointAllowingQueryHeader(true));
+
+ assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+ assertEquals(SQL_STATEMENT,
span.tags().get(TagConstants.DB_STATEMENT));
+ }
+
+ @Test
+ public void testPreIgnoresTheQueryHeaderWhenTheEndpointDisallowsIt() {
+ MockSpanAdapter span = decorate(endpointAllowingQueryHeader(false));
+
+ // allowQueryFromHeader is disabled by default, so this statement was
never executed and
+ // tagging it would put sender-controlled text into the span
+ assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+ assertNull(span.tags().get(TagConstants.DB_STATEMENT));
+ }
+
+ @Test
+ public void testPreIgnoresTheQueryHeaderForAnEndpointWithoutTheOption() {
+ // e.g. jdbc, which takes its query from the message body
+ MockSpanAdapter span = decorate(Mockito.mock(Endpoint.class));
+
+ assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+ assertNull(span.tags().get(TagConstants.DB_STATEMENT));
+ }
+
+ @Test
+ public void testQueryHeaderIsNotHonouredWhenTheComponentIsUnknown() {
+ DefaultEndpoint endpoint = Mockito.mock(DefaultEndpoint.class);
+ Mockito.when(endpoint.getComponent()).thenReturn(null);
+
+ assertFalse(SqlQueryHeaderHelper.isQueryHeaderHonoured(endpoint));
+ }
+
+ private static MockSpanAdapter decorate(Endpoint endpoint) {
Exchange exchange = Mockito.mock(Exchange.class);
Message message = Mockito.mock(Message.class);
@@ -42,13 +82,44 @@ public class SqlSpanDecoratorTest {
Mockito.when(message.getHeader(SqlSpanDecorator.CAMEL_SQL_QUERY,
String.class)).thenReturn(SQL_STATEMENT);
SpanDecorator decorator = new SqlSpanDecorator();
-
MockSpanAdapter span = new MockSpanAdapter();
-
decorator.beforeTracingEvent(span, exchange, endpoint);
+ return span;
+ }
- assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
- assertEquals(SQL_STATEMENT,
span.tags().get(TagConstants.DB_STATEMENT));
+ private static Endpoint endpointAllowingQueryHeader(boolean allow) {
+ Component component = Mockito.mock(Component.class);
+ Mockito.when(component.getEndpointPropertyConfigurer()).thenReturn(new
AllowQueryFromHeaderConfigurer(allow));
+
+ DefaultEndpoint endpoint = Mockito.mock(DefaultEndpoint.class);
+ Mockito.when(endpoint.getComponent()).thenReturn(component);
+ return endpoint;
}
+ /**
+ * Stands in for the generated {@code SqlEndpointConfigurer}, which
camel-telemetry cannot depend on.
+ */
+ private static class AllowQueryFromHeaderConfigurer implements
PropertyConfigurer, PropertyConfigurerGetter {
+
+ private final boolean allow;
+
+ AllowQueryFromHeaderConfigurer(boolean allow) {
+ this.allow = allow;
+ }
+
+ @Override
+ public boolean configure(CamelContext camelContext, Object target,
String name, Object value, boolean ignoreCase) {
+ return false;
+ }
+
+ @Override
+ public Class<?> getOptionType(String name, boolean ignoreCase) {
+ return "allowQueryFromHeader".equals(name) ? boolean.class : null;
+ }
+
+ @Override
+ public Object getOptionValue(Object target, String name, boolean
ignoreCase) {
+ return "allowQueryFromHeader".equals(name) ? allow : null;
+ }
+ }
}
diff --git
a/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlQueryHeaderHelper.java
b/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlQueryHeaderHelper.java
new file mode 100644
index 000000000000..d3e21c0ebeec
--- /dev/null
+++
b/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlQueryHeaderHelper.java
@@ -0,0 +1,57 @@
+/*
+ * 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.tracing.decorators;
+
+import org.apache.camel.Component;
+import org.apache.camel.Endpoint;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.support.DefaultEndpoint;
+
+/**
+ * Determines whether an endpoint honours the {@code CamelSqlQuery} header.
+ * <p/>
+ * The camel-sql components gate that header behind the {@code
allowQueryFromHeader} option, which is disabled by
+ * default. When it is disabled the header is ignored and the
endpoint-configured query is executed instead, so
+ * surfacing the header value would attribute a statement to the exchange that
never ran, and would place
+ * sender-controlled text into telemetry.
+ * <p/>
+ * The option is read through the endpoint's generated {@link
PropertyConfigurer} rather than by casting, because the
+ * tracing modules must not depend on camel-sql. An endpoint that does not
declare the option at all (jdbc, for
+ * instance) never honours the header.
+ */
+final class SqlQueryHeaderHelper {
+
+ private static final String ALLOW_QUERY_FROM_HEADER =
"allowQueryFromHeader";
+
+ private SqlQueryHeaderHelper() {
+ }
+
+ static boolean isQueryHeaderHonoured(Endpoint endpoint) {
+ if (!(endpoint instanceof DefaultEndpoint defaultEndpoint)) {
+ return false;
+ }
+ Component component = defaultEndpoint.getComponent();
+ if (component == null) {
+ return false;
+ }
+ if (component.getEndpointPropertyConfigurer() instanceof
PropertyConfigurerGetter getter) {
+ return Boolean.TRUE.equals(getter.getOptionValue(endpoint,
ALLOW_QUERY_FROM_HEADER, true));
+ }
+ return false;
+ }
+}
diff --git
a/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlSpanDecorator.java
b/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlSpanDecorator.java
index 8077de5305df..b6709c84d223 100644
---
a/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlSpanDecorator.java
+++
b/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlSpanDecorator.java
@@ -21,6 +21,8 @@ import org.apache.camel.Exchange;
import org.apache.camel.tracing.SpanAdapter;
import org.apache.camel.tracing.TagConstants;
+import static
org.apache.camel.tracing.decorators.SqlQueryHeaderHelper.isQueryHeaderHonoured;
+
@Deprecated(since = "4.19.0")
public class SqlSpanDecorator extends AbstractSpanDecorator {
@@ -41,9 +43,13 @@ public class SqlSpanDecorator extends AbstractSpanDecorator {
super.pre(span, exchange, endpoint);
span.setTag(TagConstants.DB_SYSTEM, "sql");
- String query = exchange.getIn().getHeader(CAMEL_SQL_QUERY,
String.class);
- if (query != null) {
- span.setTag(TagConstants.DB_STATEMENT, query);
+ // the header only reaches the database when the endpoint opts in, so
tagging it
+ // unconditionally would report a statement that was never executed
+ if (isQueryHeaderHonoured(endpoint)) {
+ String query = exchange.getIn().getHeader(CAMEL_SQL_QUERY,
String.class);
+ if (query != null) {
+ span.setTag(TagConstants.DB_STATEMENT, query);
+ }
}
}
diff --git
a/components/camel-tracing/src/test/java/org/apache/camel/tracing/decorators/SqlSpanDecoratorTest.java
b/components/camel-tracing/src/test/java/org/apache/camel/tracing/decorators/SqlSpanDecoratorTest.java
index 3df32dee7d67..f06b6780d2a2 100644
---
a/components/camel-tracing/src/test/java/org/apache/camel/tracing/decorators/SqlSpanDecoratorTest.java
+++
b/components/camel-tracing/src/test/java/org/apache/camel/tracing/decorators/SqlSpanDecoratorTest.java
@@ -16,9 +16,14 @@
*/
package org.apache.camel.tracing.decorators;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Component;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.support.DefaultEndpoint;
import org.apache.camel.tracing.MockSpanAdapter;
import org.apache.camel.tracing.SpanDecorator;
import org.apache.camel.tracing.TagConstants;
@@ -26,6 +31,8 @@ import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
@Deprecated(since = "4.19.0")
public class SqlSpanDecoratorTest {
@@ -33,8 +40,41 @@ public class SqlSpanDecoratorTest {
private static final String SQL_STATEMENT = "select * from customer";
@Test
- public void testPre() {
- Endpoint endpoint = Mockito.mock(Endpoint.class);
+ public void testPreTagsTheQueryHeaderWhenTheEndpointAllowsIt() {
+ MockSpanAdapter span = decorate(endpointAllowingQueryHeader(true));
+
+ assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+ assertEquals(SQL_STATEMENT,
span.tags().get(TagConstants.DB_STATEMENT));
+ }
+
+ @Test
+ public void testPreIgnoresTheQueryHeaderWhenTheEndpointDisallowsIt() {
+ MockSpanAdapter span = decorate(endpointAllowingQueryHeader(false));
+
+ // allowQueryFromHeader is disabled by default, so this statement was
never executed and
+ // tagging it would put sender-controlled text into the span
+ assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+ assertNull(span.tags().get(TagConstants.DB_STATEMENT));
+ }
+
+ @Test
+ public void testPreIgnoresTheQueryHeaderForAnEndpointWithoutTheOption() {
+ // e.g. jdbc, which takes its query from the message body
+ MockSpanAdapter span = decorate(Mockito.mock(Endpoint.class));
+
+ assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+ assertNull(span.tags().get(TagConstants.DB_STATEMENT));
+ }
+
+ @Test
+ public void testQueryHeaderIsNotHonouredWhenTheComponentIsUnknown() {
+ DefaultEndpoint endpoint = Mockito.mock(DefaultEndpoint.class);
+ Mockito.when(endpoint.getComponent()).thenReturn(null);
+
+ assertFalse(SqlQueryHeaderHelper.isQueryHeaderHonoured(endpoint));
+ }
+
+ private static MockSpanAdapter decorate(Endpoint endpoint) {
Exchange exchange = Mockito.mock(Exchange.class);
Message message = Mockito.mock(Message.class);
@@ -43,13 +83,44 @@ public class SqlSpanDecoratorTest {
Mockito.when(message.getHeader(SqlSpanDecorator.CAMEL_SQL_QUERY,
String.class)).thenReturn(SQL_STATEMENT);
SpanDecorator decorator = new SqlSpanDecorator();
-
MockSpanAdapter span = new MockSpanAdapter();
-
decorator.pre(span, exchange, endpoint);
+ return span;
+ }
- assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
- assertEquals(SQL_STATEMENT,
span.tags().get(TagConstants.DB_STATEMENT));
+ private static Endpoint endpointAllowingQueryHeader(boolean allow) {
+ Component component = Mockito.mock(Component.class);
+ Mockito.when(component.getEndpointPropertyConfigurer()).thenReturn(new
AllowQueryFromHeaderConfigurer(allow));
+
+ DefaultEndpoint endpoint = Mockito.mock(DefaultEndpoint.class);
+ Mockito.when(endpoint.getComponent()).thenReturn(component);
+ return endpoint;
}
+ /**
+ * Stands in for the generated {@code SqlEndpointConfigurer}, which
camel-tracing cannot depend on.
+ */
+ private static class AllowQueryFromHeaderConfigurer implements
PropertyConfigurer, PropertyConfigurerGetter {
+
+ private final boolean allow;
+
+ AllowQueryFromHeaderConfigurer(boolean allow) {
+ this.allow = allow;
+ }
+
+ @Override
+ public boolean configure(CamelContext camelContext, Object target,
String name, Object value, boolean ignoreCase) {
+ return false;
+ }
+
+ @Override
+ public Class<?> getOptionType(String name, boolean ignoreCase) {
+ return "allowQueryFromHeader".equals(name) ? boolean.class : null;
+ }
+
+ @Override
+ public Object getOptionValue(Object target, String name, boolean
ignoreCase) {
+ return "allowQueryFromHeader".equals(name) ? allow : null;
+ }
+ }
}
diff --git
a/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
index c99504cf7aff..ca8a4578f10a 100644
---
a/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
+++
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
@@ -25,12 +25,16 @@ import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.camel.Component;
+import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.NonManagedService;
import org.apache.camel.spi.CamelEvent;
import org.apache.camel.spi.Configurer;
import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.PropertyConfigurerGetter;
import org.apache.camel.spi.annotations.DevConsole;
+import org.apache.camel.support.DefaultEndpoint;
import org.apache.camel.support.EventNotifierSupport;
import org.apache.camel.support.ResourceHelper;
import org.apache.camel.support.console.AbstractDevConsole;
@@ -234,6 +238,29 @@ public class SqlTraceDevConsole extends AbstractDevConsole
{
return "resource:" + uri;
}
+ /**
+ * Whether the endpoint honours the {@code CamelSqlQuery} header.
+ * <p/>
+ * camel-sql gates that header behind the {@code allowQueryFromHeader}
option, disabled by default; when it is
+ * disabled the endpoint-configured query runs instead, so reporting the
header would show a statement that never
+ * executed. The option is read through the generated property configurer
rather than by casting, because
+ * camel-console must not depend on camel-sql. Endpoints that do not
declare the option at all - jdbc, which takes
+ * its query from the body - never honour the header.
+ */
+ private static boolean isQueryHeaderHonoured(Endpoint endpoint) {
+ if (!(endpoint instanceof DefaultEndpoint defaultEndpoint)) {
+ return false;
+ }
+ Component component = defaultEndpoint.getComponent();
+ if (component == null) {
+ return false;
+ }
+ if (component.getEndpointPropertyConfigurer() instanceof
PropertyConfigurerGetter getter) {
+ return Boolean.TRUE.equals(getter.getOptionValue(endpoint,
"allowQueryFromHeader", true));
+ }
+ return false;
+ }
+
private static String detectCategory(String query) {
if (query != null && !query.isEmpty()) {
String upper = query.stripLeading().toUpperCase(Locale.ENGLISH);
@@ -273,11 +300,15 @@ public class SqlTraceDevConsole extends
AbstractDevConsole {
if (uri.startsWith("sql:") || uri.startsWith("jdbc:")) {
Exchange exchange = ese.getExchange();
- // prefer the CamelSqlQuery header (runtime override) over
the URI
+ // prefer the CamelSqlQuery header (runtime override) over
the URI, but only when the
+ // endpoint actually honours it - otherwise the console
would report a statement that
+ // was never executed
String query = null;
- Object headerQuery =
exchange.getMessage().getHeader("CamelSqlQuery");
- if (headerQuery != null) {
- query = headerQuery.toString();
+ if (isQueryHeaderHonoured(ese.getEndpoint())) {
+ Object headerQuery =
exchange.getMessage().getHeader("CamelSqlQuery");
+ if (headerQuery != null) {
+ query = headerQuery.toString();
+ }
}
if (query == null) {
query = extractQuery(uri);
diff --git
a/core/camel-console/src/test/java/org/apache/camel/impl/console/SqlTraceDevConsoleQueryHeaderTest.java
b/core/camel-console/src/test/java/org/apache/camel/impl/console/SqlTraceDevConsoleQueryHeaderTest.java
new file mode 100644
index 000000000000..f26d3a703d6d
--- /dev/null
+++
b/core/camel-console/src/test/java/org/apache/camel/impl/console/SqlTraceDevConsoleQueryHeaderTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.impl.console;
+
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Consumer;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Endpoint;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.Producer;
+import org.apache.camel.console.DevConsole;
+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.DefaultProducer;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The {@code CamelSqlQuery} header only changes the executed statement when
the endpoint sets
+ * {@code allowQueryFromHeader=true}, which is off by default. The console
must not report the header as the executed
+ * query when the endpoint ignored it - the statement never ran, and the
header is sender-controlled.
+ *
+ * @see <a
href="https://issues.apache.org/jira/browse/CAMEL-24718">CAMEL-24718</a>
+ */
+public class SqlTraceDevConsoleQueryHeaderTest extends ContextTestSupport {
+
+ private static final String ENDPOINT_QUERY = "select * from projects";
+ private static final String HEADER_QUERY = "select * from secrets";
+
+ @Test
+ public void testHeaderQueryIsIgnoredWhenTheEndpointDisallowsIt() throws
Exception {
+ Assertions.assertEquals(ENDPOINT_QUERY, tracedQuery(false));
+ }
+
+ @Test
+ public void testHeaderQueryIsReportedWhenTheEndpointAllowsIt() throws
Exception {
+ Assertions.assertEquals(HEADER_QUERY, tracedQuery(true));
+ }
+
+ private String tracedQuery(boolean allowQueryFromHeader) throws Exception {
+ context.addComponent("sql", new
FakeSqlComponent(allowQueryFromHeader));
+
+ DevConsole con =
PluginHelper.getDevConsoleResolver(context).resolveDevConsole("sql-trace");
+ Assertions.assertNotNull(con);
+ ServiceHelper.startService(con);
+
+ template.sendBodyAndHeader("sql:" + ENDPOINT_QUERY, "body",
"CamelSqlQuery", HEADER_QUERY);
+
+ JsonObject out = (JsonObject) con.call(DevConsole.MediaType.JSON);
+ JsonArray statements = (JsonArray) out.get("statements");
+ Assertions.assertNotNull(statements, "the console should have traced
the sql: send");
+ Assertions.assertEquals(1, statements.size());
+ return (String) ((JsonObject) statements.get(0)).get("query");
+ }
+
+ /**
+ * Stands in for camel-sql, which camel-console must not depend on.
+ */
+ private static final class FakeSqlComponent extends DefaultComponent {
+
+ private final PropertyConfigurer configurer;
+
+ private FakeSqlComponent(boolean allowQueryFromHeader) {
+ this.configurer = new
AllowQueryFromHeaderConfigurer(allowQueryFromHeader);
+ }
+
+ @Override
+ public PropertyConfigurer getEndpointPropertyConfigurer() {
+ return configurer;
+ }
+
+ @Override
+ protected Endpoint createEndpoint(String uri, String remaining,
Map<String, Object> parameters) {
+ return new FakeSqlEndpoint(uri, this);
+ }
+ }
+
+ private static final class FakeSqlEndpoint extends DefaultEndpoint {
+
+ private FakeSqlEndpoint(String uri, DefaultComponent component) {
+ super(uri, component);
+ }
+
+ @Override
+ public Producer createProducer() {
+ return new DefaultProducer(this) {
+ @Override
+ public void process(Exchange exchange) {
+ // the statement is not actually executed; only the traced
metadata matters here
+ }
+ };
+ }
+
+ @Override
+ public Consumer createConsumer(Processor processor) {
+ throw new UnsupportedOperationException("Consumer not supported");
+ }
+ }
+
+ private static final class AllowQueryFromHeaderConfigurer implements
PropertyConfigurer, PropertyConfigurerGetter {
+
+ private final boolean allow;
+
+ private AllowQueryFromHeaderConfigurer(boolean allow) {
+ this.allow = allow;
+ }
+
+ @Override
+ public boolean configure(CamelContext camelContext, Object target,
String name, Object value, boolean ignoreCase) {
+ return false;
+ }
+
+ @Override
+ public Class<?> getOptionType(String name, boolean ignoreCase) {
+ return "allowQueryFromHeader".equals(name) ? boolean.class : null;
+ }
+
+ @Override
+ public Object getOptionValue(Object target, String name, boolean
ignoreCase) {
+ return "allowQueryFromHeader".equals(name) ? allow : null;
+ }
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 1a642e457dce..aa72b55e2601 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -2340,6 +2340,13 @@ stored-procedure template text and no longer resolves it
through `SqlHelper.reso
as a resource, consistent with how `camel-sql` already treats the body under
`useMessageBodyForSql=true`. A route
that relied on the body being a resource location must resolve it to the
template text before the `sql-stored`
endpoint.
+
+Observability follows the gate. The `db.statement` span tag set by the `sql`
span decorator (camel-telemetry and
+the deprecated camel-tracing) is now taken from the `CamelSqlQuery` header
only when the endpoint sets
+`allowQueryFromHeader=true`; otherwise the tag is omitted rather than
reporting a statement that was never
+executed. A route that reads `db.statement` from a `sql:` span and relies on
the header value must set
+`allowQueryFromHeader=true`.
+
=== camel-core - the inheritErrorHandler attribute on circuitBreaker and
failoverLoadBalancer is now a String
`CircuitBreakerDefinition.inheritErrorHandler` and
`FailoverLoadBalancerDefinition.inheritErrorHandler` are now