This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch ss in repository https://gitbox.apache.org/repos/asf/camel.git
commit da140f17561643dda7d09b6e75d484d731874520 Author: Claus Ibsen <[email protected]> AuthorDate: Wed Dec 10 14:20:09 2025 +0100 CAMEL-22770: camel-sql - Stored producer should lookup named parameters like regular producer do --- .../src/main/docs/sql-stored-component.adoc | 13 +++- .../sql/DefaultSqlPrepareStatementStrategy.java | 56 +------------- .../org/apache/camel/component/sql/SqlHelper.java | 53 +++++++++++++ .../sql/stored/template/ast/InOutParameter.java | 5 +- .../sql/stored/template/ast/InParameter.java | 5 +- .../camel/component/sql/stored/ParserTest.java | 21 ++++-- .../sql/stored/ProducerNamedParameterTest.java | 87 ++++++++++++++++++++++ 7 files changed, 171 insertions(+), 69 deletions(-) diff --git a/components/camel-sql/src/main/docs/sql-stored-component.adoc b/components/camel-sql/src/main/docs/sql-stored-component.adoc index 35a01c7c15bd..da251c0f519f 100644 --- a/components/camel-sql/src/main/docs/sql-stored-component.adoc +++ b/components/camel-sql/src/main/docs/sql-stored-component.adoc @@ -59,9 +59,9 @@ template, as show: [source,sql] ---- SUBNUMBERS( - INTEGER ${headers.num1}, - INTEGER ${headers.num2}, - INOUT INTEGER ${headers.num3} out1, + INTEGER :#num, + INTEGER :#num2, + INOUT INTEGER :#num3 out1, OUT INTEGER out2 ) ---- @@ -121,6 +121,13 @@ It can be either a Simple expression or header location i.e. `:#<header name>`. the Simple expression `${header.val}` would mean that parameter value will be read from the header `val`. Header location expression `:#val` would have identical effect. +When using named parameters, Camel will look up the names in the given precedence: + +1. from a xref:languages:simple-language.adoc[Simple] expressions +2. from message body if it is a `java.util.Map` +3. from message headers +4. from exchange variables + [source,xml] ---- <to uri="sql-stored:MYFUNC('param1' org.example.Types.INTEGER(10) ${header.srcValue})"/> diff --git a/components/camel-sql/src/main/java/org/apache/camel/component/sql/DefaultSqlPrepareStatementStrategy.java b/components/camel-sql/src/main/java/org/apache/camel/component/sql/DefaultSqlPrepareStatementStrategy.java index 1c188a9bc132..69c3b8cf00ab 100644 --- a/components/camel-sql/src/main/java/org/apache/camel/component/sql/DefaultSqlPrepareStatementStrategy.java +++ b/components/camel-sql/src/main/java/org/apache/camel/component/sql/DefaultSqlPrepareStatementStrategy.java @@ -29,9 +29,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.camel.Exchange; -import org.apache.camel.Expression; import org.apache.camel.RuntimeExchangeException; -import org.apache.camel.support.ExchangeHelper; import org.apache.camel.support.ObjectHelper; import org.apache.camel.util.StringQuoteHelper; import org.slf4j.Logger; @@ -71,7 +69,7 @@ public class DefaultSqlPrepareStatementStrategy implements SqlPrepareStatementSt Matcher matcher = REPLACE_IN_PATTERN.matcher(query); while (matcher.find()) { String found = matcher.group(1); - Object parameter = lookupParameter(found, exchange, null); + Object parameter = SqlHelper.lookupParameter(found, exchange, null); if (parameter != null) { Iterator<?> it = createInParameterIterator(parameter); StringJoiner replaceBuilder = new StringJoiner(","); @@ -266,54 +264,6 @@ public class DefaultSqlPrepareStatementStrategy implements SqlPrepareStatementSt } } - protected static Object lookupParameter(String nextParam, Exchange exchange, Object batchBody) { - Object body = batchBody != null ? batchBody : exchange.getMessage().getBody(); - Map<?, ?> bodyMap = safeMap(exchange.getContext().getTypeConverter().tryConvertTo(Map.class, exchange, body)); - Map<?, ?> headersMap = safeMap(exchange.getIn().getHeaders()); - Map<?, ?> variablesMap = safeMap(exchange.getVariables()); - - Object answer = null; - if ((nextParam.startsWith("$simple{") || nextParam.startsWith("${")) && nextParam.endsWith("}")) { - if (batchBody != null) { - // in batch mode then need to work on a copy of the original exchange and set the batch body - exchange = ExchangeHelper.createCopy(exchange, true); - exchange.getMessage().setBody(batchBody); - } - Expression exp = exchange.getContext().resolveLanguage("simple").createExpression(nextParam); - answer = exp.evaluate(exchange, Object.class); - } else if (bodyMap.containsKey(nextParam)) { - answer = bodyMap.get(nextParam); - } else if (headersMap.containsKey(nextParam)) { - answer = headersMap.get(nextParam); - } else if (variablesMap.containsKey(nextParam)) { - answer = variablesMap.get(nextParam); - } - - return answer; - } - - protected static boolean hasParameter(String nextParam, Exchange exchange, Object body) { - Map<?, ?> bodyMap = safeMap(exchange.getContext().getTypeConverter().tryConvertTo(Map.class, body)); - Map<?, ?> headersMap = safeMap(exchange.getIn().getHeaders()); - Map<?, ?> variablesMap = safeMap(exchange.getVariables()); - - if ((nextParam.startsWith("$simple{") || nextParam.startsWith("${")) && nextParam.endsWith("}")) { - return true; - } else if (bodyMap.containsKey(nextParam)) { - return true; - } else if (headersMap.containsKey(nextParam)) { - return true; - } else if (variablesMap.containsKey(nextParam)) { - return true; - } - - return false; - } - - private static Map<?, ?> safeMap(Map<?, ?> map) { - return (map == null || map.isEmpty()) ? Collections.emptyMap() : map; - } - @SuppressWarnings("unchecked") protected static CompositeIterator<?> createInParameterIterator(Object value) { Iterator<?> it; @@ -367,9 +317,9 @@ public class DefaultSqlPrepareStatementStrategy implements SqlPrepareStatementSt Object next = null; try { - boolean hasNext = hasParameter(nextParam, exchange, body); + boolean hasNext = SqlHelper.hasParameter(nextParam, exchange, body); if (hasNext) { - next = lookupParameter(nextParam, exchange, body); + next = SqlHelper.lookupParameter(nextParam, exchange, body); if (in && next != null) { // if SQL IN we need to return an iterator that can iterate the parameter values next = createInParameterIterator(next); diff --git a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlHelper.java b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlHelper.java index 28df085074f2..b1f6765908fe 100644 --- a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlHelper.java +++ b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlHelper.java @@ -18,10 +18,15 @@ package org.apache.camel.component.sql; import java.io.IOException; import java.io.InputStream; +import java.util.Collections; +import java.util.Map; import java.util.StringJoiner; import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.Expression; import org.apache.camel.NoTypeConversionAvailableException; +import org.apache.camel.support.ExchangeHelper; import org.apache.camel.support.ResourceHelper; public final class SqlHelper { @@ -61,4 +66,52 @@ public final class SqlHelper { answer = sj.toString(); return answer; } + + public static Object lookupParameter(String nextParam, Exchange exchange, Object batchBody) { + Object body = batchBody != null ? batchBody : exchange.getMessage().getBody(); + Map<?, ?> bodyMap = safeMap(exchange.getContext().getTypeConverter().tryConvertTo(Map.class, exchange, body)); + Map<?, ?> headersMap = safeMap(exchange.getIn().getHeaders()); + Map<?, ?> variablesMap = safeMap(exchange.getVariables()); + + Object answer = null; + if ((nextParam.startsWith("$simple{") || nextParam.startsWith("${")) && nextParam.endsWith("}")) { + if (batchBody != null) { + // in batch mode then need to work on a copy of the original exchange and set the batch body + exchange = ExchangeHelper.createCopy(exchange, true); + exchange.getMessage().setBody(batchBody); + } + Expression exp = exchange.getContext().resolveLanguage("simple").createExpression(nextParam); + answer = exp.evaluate(exchange, Object.class); + } else if (bodyMap.containsKey(nextParam)) { + answer = bodyMap.get(nextParam); + } else if (headersMap.containsKey(nextParam)) { + answer = headersMap.get(nextParam); + } else if (variablesMap.containsKey(nextParam)) { + answer = variablesMap.get(nextParam); + } + + return answer; + } + + public static boolean hasParameter(String nextParam, Exchange exchange, Object body) { + Map<?, ?> bodyMap = safeMap(exchange.getContext().getTypeConverter().tryConvertTo(Map.class, body)); + Map<?, ?> headersMap = safeMap(exchange.getIn().getHeaders()); + Map<?, ?> variablesMap = safeMap(exchange.getVariables()); + + if ((nextParam.startsWith("$simple{") || nextParam.startsWith("${")) && nextParam.endsWith("}")) { + return true; + } else if (bodyMap.containsKey(nextParam)) { + return true; + } else if (headersMap.containsKey(nextParam)) { + return true; + } else if (variablesMap.containsKey(nextParam)) { + return true; + } + + return false; + } + + private static Map<?, ?> safeMap(Map<?, ?> map) { + return (map == null || map.isEmpty()) ? Collections.emptyMap() : map; + } } diff --git a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InOutParameter.java b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InOutParameter.java index 91c7a9018041..97bfbcd18888 100644 --- a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InOutParameter.java +++ b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InOutParameter.java @@ -16,9 +16,8 @@ */ package org.apache.camel.component.sql.stored.template.ast; -import java.util.Map; - import org.apache.camel.Expression; +import org.apache.camel.component.sql.SqlHelper; import org.apache.camel.component.sql.stored.template.generated.SSPTParserConstants; import org.apache.camel.component.sql.stored.template.generated.Token; @@ -52,7 +51,7 @@ public class InOutParameter { } else if (SSPTParserConstants.PARAMETER_POS_TOKEN == valueSrcToken.kind) { //remove leading :# final String mapKey = valueSrcToken.toString().substring(2); - this.valueExtractor = (exchange, container) -> ((Map) container).get(mapKey); + this.valueExtractor = (exchange, container) -> SqlHelper.lookupParameter(mapKey, exchange, container); } } diff --git a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InParameter.java b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InParameter.java index 5d0de673cdad..b94af655e383 100644 --- a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InParameter.java +++ b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/template/ast/InParameter.java @@ -16,9 +16,8 @@ */ package org.apache.camel.component.sql.stored.template.ast; -import java.util.Map; - import org.apache.camel.Expression; +import org.apache.camel.component.sql.SqlHelper; import org.apache.camel.component.sql.stored.template.generated.SSPTParserConstants; import org.apache.camel.component.sql.stored.template.generated.Token; @@ -52,7 +51,7 @@ public class InParameter { } else if (SSPTParserConstants.PARAMETER_POS_TOKEN == valueSrcToken.kind) { //remove leading :# final String mapKey = valueSrcToken.toString().substring(2); - this.valueExtractor = (exchange, container) -> ((Map) container).get(mapKey); + this.valueExtractor = (exchange, container) -> SqlHelper.lookupParameter(mapKey, exchange, container); } } diff --git a/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ParserTest.java b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ParserTest.java index da3aee86d580..3d0ba740bdf1 100644 --- a/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ParserTest.java +++ b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ParserTest.java @@ -21,6 +21,7 @@ import java.sql.Types; import java.util.HashMap; import java.util.Map; +import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.component.sql.stored.template.TemplateParser; import org.apache.camel.component.sql.stored.template.ast.InOutParameter; @@ -28,7 +29,9 @@ import org.apache.camel.component.sql.stored.template.ast.InParameter; import org.apache.camel.component.sql.stored.template.ast.OutParameter; import org.apache.camel.component.sql.stored.template.ast.ParseRuntimeException; import org.apache.camel.component.sql.stored.template.ast.Template; +import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.spi.PropertiesComponent; +import org.apache.camel.support.DefaultExchange; import org.apache.camel.test.junit5.CamelTestSupport; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -153,13 +156,17 @@ public class ParserTest extends CamelTestSupport { } @Test - public void nableIssueSyntax() { - Map<String, String> params = new HashMap<>(); - params.put("P_STR_IN", "a"); - Template template - = parser.parseTemplate("IBS.\"Z$IMS_INTERFACE_WS\".TEST_STR(VARCHAR :#P_STR_IN,OUT VARCHAR P_STR_OUT)"); - assertEquals("a", ((InParameter) template.getParameterList().get(0)).getValueExtractor().eval(null, params)); - assertEquals("IBS.\"Z$IMS_INTERFACE_WS\".TEST_STR", template.getProcedureName()); + public void nableIssueSyntax() throws Exception { + try (CamelContext context = new DefaultCamelContext()) { + Exchange dummy = new DefaultExchange(context); + + Map<String, String> params = new HashMap<>(); + params.put("P_STR_IN", "a"); + Template template + = parser.parseTemplate("IBS.\"Z$IMS_INTERFACE_WS\".TEST_STR(VARCHAR :#P_STR_IN,OUT VARCHAR P_STR_OUT)"); + assertEquals("a", ((InParameter) template.getParameterList().get(0)).getValueExtractor().eval(dummy, params)); + assertEquals("IBS.\"Z$IMS_INTERFACE_WS\".TEST_STR", template.getProcedureName()); + } } @Test diff --git a/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerNamedParameterTest.java b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerNamedParameterTest.java new file mode 100644 index 000000000000..f7dd6f444ad0 --- /dev/null +++ b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerNamedParameterTest.java @@ -0,0 +1,87 @@ +/* + * 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.component.sql.stored; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class ProducerNamedParameterTest extends CamelTestSupport { + + EmbeddedDatabase db; + + @Override + + public void doPreSetup() throws Exception { + db = new EmbeddedDatabaseBuilder() + .setName(getClass().getSimpleName()) + .setType(EmbeddedDatabaseType.DERBY) + .addScript("sql/storedProcedureTest.sql").build(); + + } + + @Override + public void doPostTearDown() throws Exception { + if (db != null) { + db.shutdown(); + } + } + + @Test + public void shouldExecuteStoredProcedure() throws InterruptedException { + MockEndpoint mock = getMockEndpoint("mock:query"); + mock.expectedMessageCount(1); + + Map<String, Object> headers = new HashMap<>(); + headers.put("num1", 1); + headers.put("num2", 2); + template.requestBodyAndHeaders("direct:query", null, headers); + + MockEndpoint.assertIsSatisfied(context); + + Exchange exchange = mock.getExchanges().get(0); + + assertEquals(Integer.valueOf(-1), exchange.getIn().getBody(Map.class).get("resultofsub")); + assertNotNull(exchange.getIn().getHeader(SqlStoredConstants.SQL_STORED_UPDATE_COUNT)); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + // required for the sql component + getContext().getComponent("sql-stored", SqlStoredComponent.class).setDataSource(db); + + from("direct:query").to("sql-stored:SUBNUMBERS(INTEGER :#num1,INTEGER :#num2,OUT INTEGER resultofsub)") + .to("mock:query"); + } + }; + } + +}
