This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26186-thrift-reply-conversion in repository https://gitbox.apache.org/repos/asf/camel.git
commit 9cbd450ed39651fcb0e79569ff96e15abd9c60af Author: Claus Ibsen <[email protected]> AuthorDate: Mon Nov 2 23:53:29 2026 +0100 CAMEL-26186: camel-thrift - fail the call when the consumer reply is null or does not convert, and map a single producer argument like a List one The asynchronous consumer completed the call with getBody(returnType), so a reply that did not convert was sent as a null result (or, for a method that returns a number, no response at all). The synchronous consumer sent 0 for a null reply of a method that returns a number. Both now fail the call with a TApplicationException (MISSING_RESULT for no body, INTERNAL_ERROR for a body that does not convert); a void method still ignores the body. The producer matched a single non-List body by its exact class, so an Integer body found no method with one i32 argument. It now maps it with the same primitive and interface rules as the arguments of a List body, and a null argument gives "method not found" instead of a NullPointerException. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../apache/camel/catalog/components/thrift.json | 2 +- .../camel/catalog/docs/thrift-component.adoc | 7 + .../org/apache/camel/component/thrift/thrift.json | 2 +- .../src/main/docs/thrift-component.adoc | 7 + .../camel/component/thrift/ThriftEndpoint.java | 4 +- .../apache/camel/component/thrift/ThriftUtils.java | 60 ++++--- .../thrift/server/ThriftMethodHandler.java | 40 ++++- .../component/thrift/ThriftConsumerReplyTest.java | 176 +++++++++++++++++++++ .../camel/component/thrift/ThriftUtilsTest.java | 88 +++++++++++ .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 8 + 10 files changed, 360 insertions(+), 34 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/thrift.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/thrift.json index c5bcd0520b7d..f4c19d7a30f2 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/thrift.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/thrift.json @@ -34,7 +34,7 @@ }, "bodyTypes": { "consumerOut": { "carriers": [ "list", "null" ], "byOption": [ { "option": "synchronous", "value": "true", "carriers": [ "list" ] } ], "description": "A List of the arguments of the called method, in the order of the IDL, with the method name in the CamelThriftMethodName header. A method without arguments gives no body, or an empty List with synchronous=true" }, - "consumerReply": { "carriers": [ "any" ], "description": "The result of the method: the body is converted to its return type, so text works for an i32, and a struct result needs the Thrift struct. Ignored for a void method. A body that does not convert gives the client an error, or a null result from the asynchronous server" }, + "consumerReply": { "carriers": [ "any" ], "description": "The result of the method: the body is converted to its return type, so text works for an i32, and a struct result needs the Thrift struct. Ignored for a void method. No body, or a body that does not convert, gives the client an error" }, "producerIn": { "carriers": [ "list", "pojo", "text", "null" ], "description": "A List of the arguments of the method, in the order of the IDL; a body that is not a List is the one argument, no body calls a method without arguments. A binary argument is a ByteBuffer, not a byte array; a method whose one argument is a list needs it wrapped in a List" }, "producerOut": { "carriers": [ "unknown" ], "description": "The result of the method, of its return type in the IDL: a number, a boolean, a String, a ByteBuffer, a Thrift struct, or a List, Set or Map; no body for a void or oneway method" } }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/thrift-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/thrift-component.adoc index 569183556b6b..9337cd0cf62f 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/thrift-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/thrift-component.adoc @@ -68,8 +68,15 @@ requestBody.add(new HashMap<String, Long>()); // map parameter Object responseBody = template.requestBody("direct:thrift-alltypes", requestBody); ---- +A method with one argument can also take that argument as the body itself, without a list, and it is mapped the same way: +an `Integer` body calls a method with one `i32` argument. A method whose one argument is a list still needs it wrapped in a list. + Incoming parameters in the service consumer will also be passed to the message body as a list of objects. +The body at the end of the consumer route is the result of the method, converted to its return type. For a method with a +return type, no body, or a body that does not convert to that type, fails the call: the client gets a `TApplicationException` +instead of a null result, or `0` for a numeric result. The body is ignored for a `void` method. + == Examples Below is a simple synchronous method invoke with host and port parameters diff --git a/components/camel-thrift/src/generated/resources/META-INF/org/apache/camel/component/thrift/thrift.json b/components/camel-thrift/src/generated/resources/META-INF/org/apache/camel/component/thrift/thrift.json index c5bcd0520b7d..f4c19d7a30f2 100644 --- a/components/camel-thrift/src/generated/resources/META-INF/org/apache/camel/component/thrift/thrift.json +++ b/components/camel-thrift/src/generated/resources/META-INF/org/apache/camel/component/thrift/thrift.json @@ -34,7 +34,7 @@ }, "bodyTypes": { "consumerOut": { "carriers": [ "list", "null" ], "byOption": [ { "option": "synchronous", "value": "true", "carriers": [ "list" ] } ], "description": "A List of the arguments of the called method, in the order of the IDL, with the method name in the CamelThriftMethodName header. A method without arguments gives no body, or an empty List with synchronous=true" }, - "consumerReply": { "carriers": [ "any" ], "description": "The result of the method: the body is converted to its return type, so text works for an i32, and a struct result needs the Thrift struct. Ignored for a void method. A body that does not convert gives the client an error, or a null result from the asynchronous server" }, + "consumerReply": { "carriers": [ "any" ], "description": "The result of the method: the body is converted to its return type, so text works for an i32, and a struct result needs the Thrift struct. Ignored for a void method. No body, or a body that does not convert, gives the client an error" }, "producerIn": { "carriers": [ "list", "pojo", "text", "null" ], "description": "A List of the arguments of the method, in the order of the IDL; a body that is not a List is the one argument, no body calls a method without arguments. A binary argument is a ByteBuffer, not a byte array; a method whose one argument is a list needs it wrapped in a List" }, "producerOut": { "carriers": [ "unknown" ], "description": "The result of the method, of its return type in the IDL: a number, a boolean, a String, a ByteBuffer, a Thrift struct, or a List, Set or Map; no body for a void or oneway method" } }, diff --git a/components/camel-thrift/src/main/docs/thrift-component.adoc b/components/camel-thrift/src/main/docs/thrift-component.adoc index 569183556b6b..9337cd0cf62f 100644 --- a/components/camel-thrift/src/main/docs/thrift-component.adoc +++ b/components/camel-thrift/src/main/docs/thrift-component.adoc @@ -68,8 +68,15 @@ requestBody.add(new HashMap<String, Long>()); // map parameter Object responseBody = template.requestBody("direct:thrift-alltypes", requestBody); ---- +A method with one argument can also take that argument as the body itself, without a list, and it is mapped the same way: +an `Integer` body calls a method with one `i32` argument. A method whose one argument is a list still needs it wrapped in a list. + Incoming parameters in the service consumer will also be passed to the message body as a list of objects. +The body at the end of the consumer route is the result of the method, converted to its return type. For a method with a +return type, no body, or a body that does not convert to that type, fails the call: the client gets a `TApplicationException` +instead of a null result, or `0` for a numeric result. The body is ignored for a `void` method. + == Examples Below is a simple synchronous method invoke with host and port parameters diff --git a/components/camel-thrift/src/main/java/org/apache/camel/component/thrift/ThriftEndpoint.java b/components/camel-thrift/src/main/java/org/apache/camel/component/thrift/ThriftEndpoint.java index ccce13c1a798..da5a8c3c9b28 100644 --- a/components/camel-thrift/src/main/java/org/apache/camel/component/thrift/ThriftEndpoint.java +++ b/components/camel-thrift/src/main/java/org/apache/camel/component/thrift/ThriftEndpoint.java @@ -42,8 +42,8 @@ import org.apache.camel.support.SynchronousDelegateProducer; consumerReply = @BodyType(value = Carrier.ANY, description = "The result of the method: the body is converted to its return type, so" + " text works for an i32, and a struct result needs the Thrift struct." - + " Ignored for a void method. A body that does not convert gives the client an" - + " error, or a null result from the asynchronous server"), + + " Ignored for a void method. No body, or a body that does not convert, gives" + + " the client an error"), producerIn = @BodyType(value = { Carrier.LIST, Carrier.POJO, Carrier.TEXT, Carrier.NULL }, description = "A List of the arguments of the method, in the order of the IDL; a body that" + " is not a List is the one argument, no body calls a method without" diff --git a/components/camel-thrift/src/main/java/org/apache/camel/component/thrift/ThriftUtils.java b/components/camel-thrift/src/main/java/org/apache/camel/component/thrift/ThriftUtils.java index aff764d9b6af..89e77ff2e643 100644 --- a/components/camel-thrift/src/main/java/org/apache/camel/component/thrift/ThriftUtils.java +++ b/components/camel-thrift/src/main/java/org/apache/camel/component/thrift/ThriftUtils.java @@ -307,29 +307,7 @@ public final class ThriftUtils { int idx = 0; for (Object param : paramList) { - if (param instanceof Short) { - paramsTypes[idx] = short.class; - } else if (param instanceof Long) { - paramsTypes[idx] = long.class; - } else if (param instanceof Integer) { - paramsTypes[idx] = int.class; - } else if (param instanceof Double) { - paramsTypes[idx] = double.class; - } else if (param instanceof Byte) { - paramsTypes[idx] = byte.class; - } else if (param instanceof Boolean) { - paramsTypes[idx] = boolean.class; - } else if (param instanceof List) { - paramsTypes[idx] = List.class; - } else if (param instanceof Set) { - paramsTypes[idx] = Set.class; - } else if (param instanceof Map) { - paramsTypes[idx] = Map.class; - } else if (param instanceof ByteBuffer) { - paramsTypes[idx] = ByteBuffer.class; - } else { - paramsTypes[idx] = param.getClass(); - } + paramsTypes[idx] = toParameterType(param); paramsValues[idx] = param; idx++; } @@ -337,7 +315,7 @@ public final class ThriftUtils { paramListSize = methodCallback == null ? 1 : 2; paramsTypes = new Class[paramListSize]; paramsValues = new Object[paramListSize]; - paramsTypes[0] = request.getClass(); + paramsTypes[0] = toParameterType(request); paramsValues[0] = request; } else { paramListSize = methodCallback == null ? 0 : 1; @@ -350,4 +328,38 @@ public final class ThriftUtils { } return new Object[] { paramsTypes, paramsValues }; } + + /** + * The type of the parameter of the Thrift client method that takes the given argument: a primitive for a boxed + * number or boolean, the interface for a list, set or map + */ + @SuppressWarnings({ "rawtypes" }) + private static Class toParameterType(Object param) { + if (param == null) { + // no type to match a method by (reported as null when no method is found) + return null; + } else if (param instanceof Short) { + return short.class; + } else if (param instanceof Long) { + return long.class; + } else if (param instanceof Integer) { + return int.class; + } else if (param instanceof Double) { + return double.class; + } else if (param instanceof Byte) { + return byte.class; + } else if (param instanceof Boolean) { + return boolean.class; + } else if (param instanceof List) { + return List.class; + } else if (param instanceof Set) { + return Set.class; + } else if (param instanceof Map) { + return Map.class; + } else if (param instanceof ByteBuffer) { + return ByteBuffer.class; + } else { + return param.getClass(); + } + } } diff --git a/components/camel-thrift/src/main/java/org/apache/camel/component/thrift/server/ThriftMethodHandler.java b/components/camel-thrift/src/main/java/org/apache/camel/component/thrift/server/ThriftMethodHandler.java index 5a0f93e896d2..e656cffe95cb 100644 --- a/components/camel-thrift/src/main/java/org/apache/camel/component/thrift/server/ThriftMethodHandler.java +++ b/components/camel-thrift/src/main/java/org/apache/camel/component/thrift/server/ThriftMethodHandler.java @@ -21,6 +21,7 @@ import java.util.Arrays; import javassist.util.proxy.MethodHandler; import org.apache.camel.Exchange; +import org.apache.camel.InvalidPayloadException; import org.apache.camel.Message; import org.apache.camel.component.thrift.ThriftConstants; import org.apache.camel.component.thrift.ThriftConsumer; @@ -76,7 +77,14 @@ public class ThriftMethodHandler implements MethodHandler { callback.onError(new TException("Unable to detect method return type")); return; } - callback.onComplete(message.getBody(returnType)); + Object reply; + try { + reply = toReply(thisMethod, message, returnType); + } catch (TApplicationException e) { + callback.onError(e); + return; + } + callback.onComplete(reply); } finally { consumer.releaseExchange(exchange, false); } @@ -93,14 +101,10 @@ public class ThriftMethodHandler implements MethodHandler { // let the server send the error to the client (a declared exception as such) throw toClientException(thisMethod, exchange.getException()); } - responseBody = exchange.getIn().getBody(thisMethod.getReturnType()); + responseBody = toReply(thisMethod, exchange.getMessage(), thisMethod.getReturnType()); } finally { consumer.releaseExchange(exchange, false); } - - if (responseBody == null && !thisMethod.getReturnType().equals(Void.TYPE)) { - throw new TApplicationException("Return type requires not empty body"); - } return responseBody; } @@ -110,6 +114,30 @@ public class ThriftMethodHandler implements MethodHandler { } } + /** + * The reply of the exchange converted to the return type of the method. A void method has no reply; for any other + * method a reply that is null, or that does not convert to the return type, fails the call: a primitive return type + * must not get its default value, and the asynchronous server must not send a null result. + */ + private static Object toReply(Method method, Message message, Class<?> returnType) throws TApplicationException { + if (returnType == Void.TYPE || returnType == Void.class) { + return null; + } + if (message.getBody() == null) { + throw new TApplicationException(TApplicationException.MISSING_RESULT, "Return type requires not empty body"); + } + try { + return message.getMandatoryBody(returnType); + } catch (InvalidPayloadException e) { + TApplicationException answer = new TApplicationException( + TApplicationException.INTERNAL_ERROR, + "The reply of method " + method.getName() + " cannot be converted to its return type " + + returnType.getName()); + answer.initCause(e); + throw answer; + } + } + /** * The error to send to the client for a failed exchange. An exception that the Thrift method declares belongs to * the service contract and is sent as it is; any other one is replaced by a generic error unless muteException is diff --git a/components/camel-thrift/src/test/java/org/apache/camel/component/thrift/ThriftConsumerReplyTest.java b/components/camel-thrift/src/test/java/org/apache/camel/component/thrift/ThriftConsumerReplyTest.java new file mode 100644 index 000000000000..6d6d8b4d4b89 --- /dev/null +++ b/components/camel-thrift/src/test/java/org/apache/camel/component/thrift/ThriftConsumerReplyTest.java @@ -0,0 +1,176 @@ +/* + * 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.thrift; + +import java.util.List; +import java.util.Map; + +import org.apache.camel.Message; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.thrift.generated.Calculator; +import org.apache.camel.component.thrift.generated.Operation; +import org.apache.camel.component.thrift.generated.Work; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.apache.thrift.TApplicationException; +import org.apache.thrift.TConfiguration; +import org.apache.thrift.protocol.TBinaryProtocol; +import org.apache.thrift.transport.TSocket; +import org.apache.thrift.transport.TTransport; +import org.apache.thrift.transport.layered.TFramedTransport; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * The reply of the consumer route is converted to the return type of the method: a reply that is null, or that does not + * convert, must give the client an error from the synchronous and the asynchronous server, not a null or 0 result. + */ +public class ThriftConsumerReplyTest extends CamelTestSupport { + + private static final int CLIENT_TIMEOUT = 5000; + + // the first argument of the call selects the reply of the route + private static final int REPLY_NULL = 1; + private static final int REPLY_MAP = 2; + private static final int REPLY_TEXT = 3; + private static final int REPLY_SAME = 4; + + @ParameterizedTest + @ValueSource(strings = { "sync", "async" }) + public void testNullReplyOfIntMethodIsAnError(String routeId) throws Exception { + TTransport transport = open(routeId); + try { + Calculator.Client client = client(transport); + + TApplicationException e = assertThrows(TApplicationException.class, () -> client.add(REPLY_NULL, 0)); + assertEquals(TApplicationException.MISSING_RESULT, e.getType()); + // the connection is still usable + assertEquals(25, client.add(REPLY_TEXT, 0)); + } finally { + transport.close(); + } + } + + @ParameterizedTest + @ValueSource(strings = { "sync", "async" }) + public void testReplyOfIntMethodThatDoesNotConvertIsAnError(String routeId) throws Exception { + TTransport transport = open(routeId); + try { + Calculator.Client client = client(transport); + + TApplicationException e = assertThrows(TApplicationException.class, () -> client.add(REPLY_MAP, 0)); + assertEquals(TApplicationException.INTERNAL_ERROR, e.getType()); + assertEquals(25, client.add(REPLY_TEXT, 0)); + } finally { + transport.close(); + } + } + + @ParameterizedTest + @ValueSource(strings = { "sync", "async" }) + public void testReplyOfStructMethodThatIsNullOrDoesNotConvertIsAnError(String routeId) throws Exception { + TTransport transport = open(routeId); + try { + Calculator.Client client = client(transport); + + TApplicationException e + = assertThrows(TApplicationException.class, () -> client.echo(work(REPLY_NULL))); + assertEquals(TApplicationException.MISSING_RESULT, e.getType()); + e = assertThrows(TApplicationException.class, () -> client.echo(work(REPLY_TEXT))); + assertEquals(TApplicationException.INTERNAL_ERROR, e.getType()); + e = assertThrows(TApplicationException.class, () -> client.echo(work(REPLY_MAP))); + assertEquals(TApplicationException.INTERNAL_ERROR, e.getType()); + assertEquals(work(REPLY_SAME), client.echo(work(REPLY_SAME))); + } finally { + transport.close(); + } + } + + @ParameterizedTest + @ValueSource(strings = { "sync", "async" }) + public void testReplyOfVoidMethodIsIgnored(String routeId) throws Exception { + TTransport transport = open(routeId); + try { + Calculator.Client client = client(transport); + + // the route replies with a Map, which a void method ignores + client.ping(); + assertEquals(25, client.add(REPLY_TEXT, 0)); + } finally { + transport.close(); + } + } + + private static Work work(int num1) { + return new Work(num1, 13, Operation.MULTIPLY); + } + + private static Calculator.Client client(TTransport transport) throws Exception { + return new Calculator.Client(new TBinaryProtocol(new TFramedTransport(transport))); + } + + private TTransport open(String routeId) throws Exception { + // a timeout so that a call the server never answers fails the test + TTransport transport = new TSocket(new TConfiguration(), "localhost", getPort(routeId), CLIENT_TIMEOUT); + transport.open(); + return transport; + } + + private int getPort(String routeId) { + return ((ThriftConsumer) context.getRoute(routeId).getConsumer()).getLocalPort(); + } + + private static void reply(Message message) { + List<?> args = message.getBody(List.class); + Object first = args == null || args.isEmpty() ? null : args.get(0); + int selector = first instanceof Work ? ((Work) first).num1 : first instanceof Integer ? (Integer) first : REPLY_MAP; + switch (selector) { + case REPLY_NULL: + message.setBody(null); + break; + case REPLY_TEXT: + message.setBody("25"); + break; + case REPLY_SAME: + message.setBody(first); + break; + default: + message.setBody(Map.of("result", 25)); + } + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("thrift://localhost:0/org.apache.camel.component.thrift.generated.Calculator?synchronous=true") + .routeId("sync") + .to("direct:service"); + + from("thrift://localhost:0/org.apache.camel.component.thrift.generated.Calculator") + .routeId("async") + .to("direct:service"); + + from("direct:service") + .process(exchange -> reply(exchange.getMessage())); + } + }; + } +} diff --git a/components/camel-thrift/src/test/java/org/apache/camel/component/thrift/ThriftUtilsTest.java b/components/camel-thrift/src/test/java/org/apache/camel/component/thrift/ThriftUtilsTest.java new file mode 100644 index 000000000000..2bf297d630ef --- /dev/null +++ b/components/camel-thrift/src/test/java/org/apache/camel/component/thrift/ThriftUtilsTest.java @@ -0,0 +1,88 @@ +/* + * 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.thrift; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * A body that is not a List is the one argument of the method, and is matched to its parameter type with the same rules + * as the arguments in a List body. + */ +public class ThriftUtilsTest { + + @Test + public void testSingleIntegerBodyCallsMethodWithOneIntArgument() { + assertEquals(42, ThriftUtils.invokeSyncMethod(new FakeClient(), "twice", 21)); + assertEquals(42, ThriftUtils.invokeSyncMethod(new FakeClient(), "twice", List.of(21))); + } + + @Test + public void testSingleBoxedBodiesCallMethodsWithOnePrimitiveArgument() { + assertEquals(42L, ThriftUtils.invokeSyncMethod(new FakeClient(), "twiceLong", 21L)); + assertEquals(false, ThriftUtils.invokeSyncMethod(new FakeClient(), "not", true)); + } + + @Test + public void testSingleMapOrSetBodyCallsMethodWithOneMapOrSetArgument() { + Map<String, Integer> map = new HashMap<>(); + map.put("a", 1); + assertEquals(1, ThriftUtils.invokeSyncMethod(new FakeClient(), "size", map)); + Set<String> set = new HashSet<>(Arrays.asList("a", "b")); + assertEquals(2, ThriftUtils.invokeSyncMethod(new FakeClient(), "count", set)); + } + + @Test + public void testNullArgumentInListIsReportedAsMethodNotFound() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> ThriftUtils.invokeSyncMethod(new FakeClient(), "twice", Arrays.asList((Object) null))); + assertTrue(e.getMessage().endsWith(".twice(null)"), e.getMessage()); + } + + public static class FakeClient { + + public int twice(int value) { + return value * 2; + } + + public long twiceLong(long value) { + return value * 2; + } + + public boolean not(boolean value) { + return !value; + } + + public int size(Map<String, Integer> value) { + return value.size(); + } + + public int count(Set<String> value) { + return value.size(); + } + } +} diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc index 10f5a47c1b89..4beebbfb96bd 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc @@ -1896,6 +1896,14 @@ does not declare. Before, the asynchronous consumer sent the message of the exce sent a `TApplicationException` as it was, such as one returned by another Thrift service that the route called. An exception declared in the IDL is still sent as it is. Set `muteException=false` to send the exception as before. +=== camel-thrift - the reply of the consumer must convert to the return type of the method + +For a method with a return type, the consumer now fails the call with a `TApplicationException` when the route leaves +no body (type `MISSING_RESULT`), or a body that does not convert to the return type (type `INTERNAL_ERROR`). Before, +the synchronous consumer sent `0` (or `false`) for a method that returns a number (or a `bool`) and the route left no +body, and the asynchronous consumer sent a null result, or no response at all for a method that returns a number. The +body is still ignored for a `void` method. + === camel-netty - errors sent to the client A new `muteException` consumer option, enabled by default, makes a consumer with `sync=true` write a generic exception
