This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26264-consul-optional-body in repository https://gitbox.apache.org/repos/asf/camel.git
commit 24b45fa74c1e19ce8a1f72b14058dbcc12f33053 Author: Claus Ibsen <[email protected]> AuthorDate: Tue Nov 3 15:36:08 2026 +0100 CAMEL-26264: camel-consul - fire an event without body, no Optional in the body, and CamelConsulResult for every action The FIRE action of consul:event passed a null payload to the Consul client when the message had no body, which failed with a NullPointerException in okhttp; it now fires the event without payload. GET_SESSIONS, session RENEW and preparedQuery GET put the Optional of the client in the body, so CamelConsulResult was always true; they now unwrap it, as session INFO does. The agent and status actions now set CamelConsulResult, and catalog REGISTER and DEREGISTER set it to true instead of false. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../apache/camel/catalog/components/consul.json | 4 +- .../ConsulAgentProducerInvokeOnHeaderFactory.java | 8 +- .../ConsulStatusProducerInvokeOnHeaderFactory.java | 4 +- .../org/apache/camel/component/consul/consul.json | 4 +- .../camel/component/consul/ConsulEndpoint.java | 22 ++- .../consul/endpoint/ConsulAgentProducer.java | 18 +- .../consul/endpoint/ConsulCatalogProducer.java | 4 +- .../consul/endpoint/ConsulEventProducer.java | 15 +- .../consul/endpoint/ConsulKeyValueProducer.java | 3 +- .../endpoint/ConsulPreparedQueryProducer.java | 4 +- .../consul/endpoint/ConsulSessionProducer.java | 5 +- .../consul/endpoint/ConsulStatusProducer.java | 8 +- .../component/consul/ConsulProducerResultTest.java | 210 +++++++++++++++++++++ .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 14 ++ 14 files changed, 280 insertions(+), 43 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/consul.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/consul.json index 5e1b82d6b738..2a6518e65be3 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/consul.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/consul.json @@ -86,8 +86,8 @@ }, "bodyTypes": { "consumerOut": { "carriers": [ "text", "null" ], "description": "The kv consumer: the value of the key (of each key under it with recursive) base64 encoded as Consul stores it, decoded with valueAsString=true, with the key, flags and indexes in headers; null for a key without value. The event consumer: the payload of the event as text, with its name and id in headers" }, - "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": "action", "value": "PUT", "carriers": [ "text", "bytes", "stream", "file", "value", "null" ] }, { "option": "action", "value": "LOCK", "carriers": [ "text", "bytes", "stream", "file", "value", "null" ] }, { "option": "action", "value": "FIRE", "carriers": [ "text", "bytes", "stream", "file", "value" ] }, { "option": "action", "value": "REGISTER", "carriers": [ "pojo" ] }, { "option": "action", "value": "CREATE", "carrie [...] - "producerOut": { "carriers": [ "pojo", "list", "map", "text", "unchanged" ], "byOption": [ { "option": "action", "value": "CHECKS", "carriers": [ "map", "list" ] }, { "option": "action", "value": "SERVICES", "carriers": [ "map" ] }, { "option": "action", "value": "MEMBERS", "carriers": [ "list" ] }, { "option": "action", "value": "AGENT", "carriers": [ "pojo" ], "javaType": "org.kiwiproject.consul.model.agent.Agent" }, { "option": "action", "value": "REGISTER", "carriers": [ "unchang [...] + "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": "action", "value": "PUT", "carriers": [ "text", "bytes", "stream", "file", "value", "null" ] }, { "option": "action", "value": "LOCK", "carriers": [ "text", "bytes", "stream", "file", "value", "null" ] }, { "option": "action", "value": "FIRE", "carriers": [ "text", "bytes", "stream", "file", "value", "null" ] }, { "option": "action", "value": "REGISTER", "carriers": [ "pojo" ] }, { "option": "action", "value": "CREATE", [...] + "producerOut": { "carriers": [ "pojo", "list", "map", "text", "unchanged" ], "byOption": [ { "option": "action", "value": "CHECKS", "carriers": [ "map", "list" ] }, { "option": "action", "value": "SERVICES", "carriers": [ "map" ] }, { "option": "action", "value": "MEMBERS", "carriers": [ "list" ] }, { "option": "action", "value": "AGENT", "carriers": [ "pojo" ], "javaType": "org.kiwiproject.consul.model.agent.Agent" }, { "option": "action", "value": "REGISTER", "carriers": [ "unchang [...] }, "properties": { "apiEndpoint": { "index": 0, "kind": "path", "displayName": "Api Endpoint", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The API endpoint" }, diff --git a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulAgentProducerInvokeOnHeaderFactory.java b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulAgentProducerInvokeOnHeaderFactory.java index 952cf410e234..6a039b46350c 100644 --- a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulAgentProducerInvokeOnHeaderFactory.java +++ b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulAgentProducerInvokeOnHeaderFactory.java @@ -20,17 +20,17 @@ public class ConsulAgentProducerInvokeOnHeaderFactory implements InvokeOnHeaderS org.apache.camel.component.consul.endpoint.ConsulAgentProducer target = (org.apache.camel.component.consul.endpoint.ConsulAgentProducer) obj; switch (key) { case "agent": - case "AGENT": return target.invokeAgent(exchange.getMessage()); + case "AGENT": target.invokeAgent(exchange.getMessage()); return null; case "checks": - case "CHECKS": return target.invokeChecks(exchange.getMessage()); + case "CHECKS": target.invokeChecks(exchange.getMessage()); return null; case "deregister": case "DEREGISTER": target.invokeDeregister(exchange.getMessage()); return null; case "members": - case "MEMBERS": return target.invokeMembers(exchange.getMessage()); + case "MEMBERS": target.invokeMembers(exchange.getMessage()); return null; case "register": case "REGISTER": target.invokeRegister(exchange.getMessage()); return null; case "services": - case "SERVICES": return target.invokeServices(exchange.getMessage()); + case "SERVICES": target.invokeServices(exchange.getMessage()); return null; default: return InvokeOnHeaderStrategy.NO_METHOD; } } diff --git a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulStatusProducerInvokeOnHeaderFactory.java b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulStatusProducerInvokeOnHeaderFactory.java index 7fcecb1871a4..32cc476de20d 100644 --- a/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulStatusProducerInvokeOnHeaderFactory.java +++ b/components/camel-consul/src/generated/java/org/apache/camel/component/consul/endpoint/ConsulStatusProducerInvokeOnHeaderFactory.java @@ -20,9 +20,9 @@ public class ConsulStatusProducerInvokeOnHeaderFactory implements InvokeOnHeader org.apache.camel.component.consul.endpoint.ConsulStatusProducer target = (org.apache.camel.component.consul.endpoint.ConsulStatusProducer) obj; switch (key) { case "leader": - case "LEADER": return target.invokeChecks(exchange.getMessage()); + case "LEADER": target.invokeChecks(exchange.getMessage()); return null; case "peers": - case "PEERS": return target.invokePeers(exchange.getMessage()); + case "PEERS": target.invokePeers(exchange.getMessage()); return null; default: return InvokeOnHeaderStrategy.NO_METHOD; } } diff --git a/components/camel-consul/src/generated/resources/META-INF/org/apache/camel/component/consul/consul.json b/components/camel-consul/src/generated/resources/META-INF/org/apache/camel/component/consul/consul.json index 5e1b82d6b738..2a6518e65be3 100644 --- a/components/camel-consul/src/generated/resources/META-INF/org/apache/camel/component/consul/consul.json +++ b/components/camel-consul/src/generated/resources/META-INF/org/apache/camel/component/consul/consul.json @@ -86,8 +86,8 @@ }, "bodyTypes": { "consumerOut": { "carriers": [ "text", "null" ], "description": "The kv consumer: the value of the key (of each key under it with recursive) base64 encoded as Consul stores it, decoded with valueAsString=true, with the key, flags and indexes in headers; null for a key without value. The event consumer: the payload of the event as text, with its name and id in headers" }, - "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": "action", "value": "PUT", "carriers": [ "text", "bytes", "stream", "file", "value", "null" ] }, { "option": "action", "value": "LOCK", "carriers": [ "text", "bytes", "stream", "file", "value", "null" ] }, { "option": "action", "value": "FIRE", "carriers": [ "text", "bytes", "stream", "file", "value" ] }, { "option": "action", "value": "REGISTER", "carriers": [ "pojo" ] }, { "option": "action", "value": "CREATE", "carrie [...] - "producerOut": { "carriers": [ "pojo", "list", "map", "text", "unchanged" ], "byOption": [ { "option": "action", "value": "CHECKS", "carriers": [ "map", "list" ] }, { "option": "action", "value": "SERVICES", "carriers": [ "map" ] }, { "option": "action", "value": "MEMBERS", "carriers": [ "list" ] }, { "option": "action", "value": "AGENT", "carriers": [ "pojo" ], "javaType": "org.kiwiproject.consul.model.agent.Agent" }, { "option": "action", "value": "REGISTER", "carriers": [ "unchang [...] + "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": "action", "value": "PUT", "carriers": [ "text", "bytes", "stream", "file", "value", "null" ] }, { "option": "action", "value": "LOCK", "carriers": [ "text", "bytes", "stream", "file", "value", "null" ] }, { "option": "action", "value": "FIRE", "carriers": [ "text", "bytes", "stream", "file", "value", "null" ] }, { "option": "action", "value": "REGISTER", "carriers": [ "pojo" ] }, { "option": "action", "value": "CREATE", [...] + "producerOut": { "carriers": [ "pojo", "list", "map", "text", "unchanged" ], "byOption": [ { "option": "action", "value": "CHECKS", "carriers": [ "map", "list" ] }, { "option": "action", "value": "SERVICES", "carriers": [ "map" ] }, { "option": "action", "value": "MEMBERS", "carriers": [ "list" ] }, { "option": "action", "value": "AGENT", "carriers": [ "pojo" ], "javaType": "org.kiwiproject.consul.model.agent.Agent" }, { "option": "action", "value": "REGISTER", "carriers": [ "unchang [...] }, "properties": { "apiEndpoint": { "index": 0, "kind": "path", "displayName": "Api Endpoint", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The API endpoint" }, diff --git a/components/camel-consul/src/main/java/org/apache/camel/component/consul/ConsulEndpoint.java b/components/camel-consul/src/main/java/org/apache/camel/component/consul/ConsulEndpoint.java index 798e4155ea8c..3a4987f393e1 100644 --- a/components/camel-consul/src/main/java/org/apache/camel/component/consul/ConsulEndpoint.java +++ b/components/camel-consul/src/main/java/org/apache/camel/component/consul/ConsulEndpoint.java @@ -54,12 +54,13 @@ import org.kiwiproject.consul.Consul; Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE, Carrier.VALUE, Carrier.NULL }), @BodyType.When(option = "action", value = "FIRE", carriers = { - Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE, Carrier.VALUE }), + Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE, Carrier.VALUE, + Carrier.NULL }), @BodyType.When(option = "action", value = "REGISTER", carriers = Carrier.POJO), @BodyType.When(option = "action", value = "CREATE", carriers = Carrier.POJO) }, description = "PUT and LOCK store the body converted to text as the value, and FIRE sends it" - + " as the payload: a Map or a POJO is stored as its toString, so marshal it" - + " first. REGISTER, DEREGISTER of catalog and CREATE take the model object of" + + " as the payload (an event without payload for a null body): a Map or a POJO" + + " is stored as its toString, so marshal it first. REGISTER, DEREGISTER of catalog and CREATE take the model object of" + " the Consul client, such as a Registration or a Session; the session and" + " prepared query actions read the id from the body when its header is not set;" + " the other actions take their input from headers"), @@ -79,8 +80,9 @@ import org.kiwiproject.consul.Consul; carriers = { Carrier.POJO, Carrier.TEXT, Carrier.UNCHANGED }), @BodyType.When(option = "action", value = "GET_VALUES", carriers = Carrier.LIST), @BodyType.When(option = "action", value = "GET_KEYS", carriers = Carrier.LIST), - @BodyType.When(option = "action", value = "GET_SESSIONS", carriers = Carrier.POJO, - javaType = "java.util.Optional<java.lang.String>"), + @BodyType.When(option = "action", value = "GET_SESSIONS", + carriers = { Carrier.TEXT, Carrier.UNCHANGED }, + javaType = "java.lang.String"), @BodyType.When(option = "action", value = "DELETE_KEY", carriers = Carrier.UNCHANGED), @BodyType.When(option = "action", value = "DELETE_KEYS", @@ -105,10 +107,12 @@ import org.kiwiproject.consul.Consul; carriers = { Carrier.POJO, Carrier.UNCHANGED }), @BodyType.When(option = "action", value = "LIST", carriers = { Carrier.POJO, Carrier.LIST }), - @BodyType.When(option = "action", value = "RENEW", carriers = Carrier.POJO, - javaType = "java.util.Optional<org.kiwiproject.consul.model.session.SessionInfo>"), - @BodyType.When(option = "action", value = "GET", carriers = Carrier.POJO, - javaType = "java.util.Optional<org.kiwiproject.consul.model.query.StoredQuery>"), + @BodyType.When(option = "action", value = "RENEW", + carriers = { Carrier.POJO, Carrier.UNCHANGED }, + javaType = "org.kiwiproject.consul.model.session.SessionInfo"), + @BodyType.When(option = "action", value = "GET", + carriers = { Carrier.POJO, Carrier.UNCHANGED }, + javaType = "org.kiwiproject.consul.model.query.StoredQuery"), @BodyType.When(option = "action", value = "EXECUTE", carriers = Carrier.POJO, javaType = "org.kiwiproject.consul.model.query.QueryResults"), @BodyType.When(option = "action", value = "FIRE", carriers = Carrier.POJO, diff --git a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulAgentProducer.java b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulAgentProducer.java index e9e1ead1fc17..648b6dddd4c1 100644 --- a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulAgentProducer.java +++ b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulAgentProducer.java @@ -32,34 +32,36 @@ public final class ConsulAgentProducer extends AbstractConsulProducer<AgentClien } @InvokeOnHeader("CHECKS") - public Object invokeChecks(Message message) throws Exception { - return getClient().getChecks(); + public void invokeChecks(Message message) throws Exception { + setBodyAndResult(message, getClient().getChecks()); } @InvokeOnHeader("SERVICES") - public Object invokeServices(Message message) throws Exception { - return getClient().getServices(); + public void invokeServices(Message message) throws Exception { + setBodyAndResult(message, getClient().getServices()); } @InvokeOnHeader("MEMBERS") - public Object invokeMembers(Message message) throws Exception { - return getClient().getMembers(); + public void invokeMembers(Message message) throws Exception { + setBodyAndResult(message, getClient().getMembers()); } @InvokeOnHeader("AGENT") - public Object invokeAgent(Message message) throws Exception { - return getClient().getAgent(); + public void invokeAgent(Message message) throws Exception { + setBodyAndResult(message, getClient().getAgent()); } @InvokeOnHeader("REGISTER") public void invokeRegister(Message message) throws Exception { getClient().register(message.getMandatoryBody(Registration.class)); + setBodyAndResult(message, null, true); } @InvokeOnHeader("DEREGISTER") public void invokeDeregister(Message message) throws Exception { getClient().deregister(getMandatoryHeader(message, ConsulConstants.CONSUL_SERVICE_ID, String.class), buildQueryOptions(message, getConfiguration())); + setBodyAndResult(message, null, true); } } diff --git a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulCatalogProducer.java b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulCatalogProducer.java index 7278f8ff6e4f..910e5b0d9c19 100644 --- a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulCatalogProducer.java +++ b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulCatalogProducer.java @@ -35,13 +35,13 @@ public final class ConsulCatalogProducer extends AbstractConsulProducer<CatalogC @InvokeOnHeader(ConsulCatalogActions.REGISTER) protected void register(Message message) throws Exception { getClient().register(message.getMandatoryBody(CatalogRegistration.class)); - setBodyAndResult(message, null); + setBodyAndResult(message, null, true); } @InvokeOnHeader(ConsulCatalogActions.DEREGISTER) protected void deregister(Message message) throws Exception { getClient().deregister(message.getMandatoryBody(CatalogDeregistration.class)); - setBodyAndResult(message, null); + setBodyAndResult(message, null, true); } @InvokeOnHeader(ConsulCatalogActions.LIST_DATACENTERS) diff --git a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulEventProducer.java b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulEventProducer.java index 472626f22944..8bfa59e14b3c 100644 --- a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulEventProducer.java +++ b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulEventProducer.java @@ -34,11 +34,16 @@ public final class ConsulEventProducer extends AbstractConsulProducer<EventClien @InvokeOnHeader(ConsulEventActions.FIRE) protected void fire(Message message) throws Exception { - setBodyAndResult(message, - getClient().fireEvent( - getMandatoryHeader(message, ConsulConstants.CONSUL_KEY, getConfiguration().getKey(), String.class), - message.getHeader(ConsulConstants.CONSUL_OPTIONS, EventOptions.BLANK, EventOptions.class), - message.getBody(String.class))); + String name = getMandatoryHeader(message, ConsulConstants.CONSUL_KEY, getConfiguration().getKey(), String.class); + EventOptions options = message.getHeader(ConsulConstants.CONSUL_OPTIONS, EventOptions.BLANK, EventOptions.class); + String payload = message.getBody(String.class); + + // the event has no payload when the message has no body + if (payload == null) { + setBodyAndResult(message, getClient().fireEvent(name, options)); + } else { + setBodyAndResult(message, getClient().fireEvent(name, options, payload)); + } } @InvokeOnHeader(ConsulEventActions.LIST) diff --git a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulKeyValueProducer.java b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulKeyValueProducer.java index 69626d430fb6..fd320a009484 100644 --- a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulKeyValueProducer.java +++ b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulKeyValueProducer.java @@ -91,7 +91,8 @@ public final class ConsulKeyValueProducer extends AbstractConsulProducer<KeyValu @InvokeOnHeader(ConsulKeyValueActions.GET_SESSIONS) protected void getSessions(Message message) throws Exception { setBodyAndResult(message, getClient().getSession( - getMandatoryHeader(message, ConsulConstants.CONSUL_KEY, getConfiguration().getKey(), String.class))); + getMandatoryHeader(message, ConsulConstants.CONSUL_KEY, getConfiguration().getKey(), String.class)) + .orElse(null)); } @InvokeOnHeader(ConsulKeyValueActions.DELETE_KEY) diff --git a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulPreparedQueryProducer.java b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulPreparedQueryProducer.java index 14422a03913b..80568786424f 100644 --- a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulPreparedQueryProducer.java +++ b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulPreparedQueryProducer.java @@ -42,9 +42,9 @@ public final class ConsulPreparedQueryProducer extends AbstractConsulProducer<Pr String id = message.getHeader(ConsulConstants.CONSUL_PREPARED_QUERY_ID, String.class); if (ObjectHelper.isEmpty(id)) { - setBodyAndResult(message, getClient().getPreparedQuery(message.getMandatoryBody(String.class))); + setBodyAndResult(message, getClient().getPreparedQuery(message.getMandatoryBody(String.class)).orElse(null)); } else { - setBodyAndResult(message, getClient().getPreparedQuery(id)); + setBodyAndResult(message, getClient().getPreparedQuery(id).orElse(null)); } } diff --git a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulSessionProducer.java b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulSessionProducer.java index 9f22f7ad4a19..7225a7674a90 100644 --- a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulSessionProducer.java +++ b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulSessionProducer.java @@ -79,10 +79,11 @@ public final class ConsulSessionProducer extends AbstractConsulProducer<SessionC if (ObjectHelper.isEmpty(sessionId)) { setBodyAndResult(message, getClient().renewSession(message.getHeader(ConsulConstants.CONSUL_DATACENTER, String.class), - message.getMandatoryBody(String.class))); + message.getMandatoryBody(String.class)).orElse(null)); } else { setBodyAndResult(message, - getClient().renewSession(message.getHeader(ConsulConstants.CONSUL_DATACENTER, String.class), sessionId)); + getClient().renewSession(message.getHeader(ConsulConstants.CONSUL_DATACENTER, String.class), sessionId) + .orElse(null)); } } } diff --git a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulStatusProducer.java b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulStatusProducer.java index c8adf1549573..f557223a8e3b 100644 --- a/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulStatusProducer.java +++ b/components/camel-consul/src/main/java/org/apache/camel/component/consul/endpoint/ConsulStatusProducer.java @@ -30,13 +30,13 @@ public final class ConsulStatusProducer extends AbstractConsulProducer<StatusCli } @InvokeOnHeader("LEADER") - public Object invokeChecks(Message message) throws Exception { - return getClient().getLeader(); + public void invokeChecks(Message message) throws Exception { + setBodyAndResult(message, getClient().getLeader()); } @InvokeOnHeader("PEERS") - public Object invokePeers(Message message) throws Exception { - return getClient().getPeers(); + public void invokePeers(Message message) throws Exception { + setBodyAndResult(message, getClient().getPeers()); } } diff --git a/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulProducerResultTest.java b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulProducerResultTest.java new file mode 100644 index 000000000000..d4ad13b95db5 --- /dev/null +++ b/components/camel-consul/src/test/java/org/apache/camel/component/consul/ConsulProducerResultTest.java @@ -0,0 +1,210 @@ +/* + * 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.consul; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.camel.BindToRegistry; +import org.apache.camel.Exchange; +import org.apache.camel.component.consul.endpoint.ConsulAgentActions; +import org.apache.camel.component.consul.endpoint.ConsulCatalogActions; +import org.apache.camel.component.consul.endpoint.ConsulEventActions; +import org.apache.camel.component.consul.endpoint.ConsulKeyValueActions; +import org.apache.camel.component.consul.endpoint.ConsulPreparedQueryActions; +import org.apache.camel.component.consul.endpoint.ConsulSessionActions; +import org.apache.camel.component.consul.endpoint.ConsulStatusActions; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.Test; +import org.kiwiproject.consul.AgentClient; +import org.kiwiproject.consul.CatalogClient; +import org.kiwiproject.consul.Consul; +import org.kiwiproject.consul.EventClient; +import org.kiwiproject.consul.KeyValueClient; +import org.kiwiproject.consul.PreparedQueryClient; +import org.kiwiproject.consul.SessionClient; +import org.kiwiproject.consul.StatusClient; +import org.kiwiproject.consul.model.agent.Registration; +import org.kiwiproject.consul.model.catalog.CatalogRegistration; +import org.kiwiproject.consul.model.event.Event; +import org.kiwiproject.consul.model.health.Service; +import org.kiwiproject.consul.model.query.StoredQuery; +import org.kiwiproject.consul.model.session.SessionInfo; +import org.kiwiproject.consul.option.EventOptions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The producers set the response of the Consul client as the body, without the Optional of the client, and the outcome + * in the CamelConsulResult header. + */ +public class ConsulProducerResultTest extends CamelTestSupport { + + private final EventClient eventClient = mock(EventClient.class); + private final KeyValueClient keyValueClient = mock(KeyValueClient.class); + private final SessionClient sessionClient = mock(SessionClient.class); + private final PreparedQueryClient preparedQueryClient = mock(PreparedQueryClient.class); + private final AgentClient agentClient = mock(AgentClient.class); + private final StatusClient statusClient = mock(StatusClient.class); + private final CatalogClient catalogClient = mock(CatalogClient.class); + + @BindToRegistry("consul") + public Consul consul() { + Consul consul = mock(Consul.class); + when(consul.eventClient()).thenReturn(eventClient); + when(consul.keyValueClient()).thenReturn(keyValueClient); + when(consul.sessionClient()).thenReturn(sessionClient); + when(consul.preparedQueryClient()).thenReturn(preparedQueryClient); + when(consul.agentClient()).thenReturn(agentClient); + when(consul.statusClient()).thenReturn(statusClient); + when(consul.catalogClient()).thenReturn(catalogClient); + return consul; + } + + @Test + public void testFireWithoutBody() { + Event event = mock(Event.class); + when(eventClient.fireEvent(eq("my-event"), any(EventOptions.class))).thenReturn(event); + + Exchange out = send("consul:event?consulClient=#consul&key=my-event&action=" + ConsulEventActions.FIRE, null); + + // no payload: the client must not be called with a null payload, which fails in okhttp + verify(eventClient, never()).fireEvent(anyString(), any(EventOptions.class), isNull()); + assertSame(event, out.getMessage().getBody()); + assertEquals(true, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + } + + @Test + public void testFireWithBody() { + Event event = mock(Event.class); + when(eventClient.fireEvent(eq("my-event"), any(EventOptions.class), eq("hello"))).thenReturn(event); + + Exchange out = send("consul:event?consulClient=#consul&key=my-event&action=" + ConsulEventActions.FIRE, "hello"); + + assertSame(event, out.getMessage().getBody()); + assertEquals(true, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + } + + @Test + public void testGetSessions() { + when(keyValueClient.getSession("locked")).thenReturn(Optional.of("session-1")); + when(keyValueClient.getSession("free")).thenReturn(Optional.empty()); + + Exchange out = send("consul:kv?consulClient=#consul&key=locked&action=" + ConsulKeyValueActions.GET_SESSIONS, + "input"); + assertEquals("session-1", out.getMessage().getBody()); + assertEquals(true, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + + out = send("consul:kv?consulClient=#consul&key=free&action=" + ConsulKeyValueActions.GET_SESSIONS, "input"); + assertEquals("input", out.getMessage().getBody()); + assertEquals(false, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + } + + @Test + public void testRenewSession() { + SessionInfo info = mock(SessionInfo.class); + when(sessionClient.renewSession(isNull(), eq("session-1"))).thenReturn(Optional.of(info)); + when(sessionClient.renewSession(isNull(), eq("session-2"))).thenReturn(Optional.empty()); + + Exchange out = send("consul:session?consulClient=#consul&action=" + ConsulSessionActions.RENEW, "session-1"); + assertSame(info, out.getMessage().getBody()); + assertEquals(true, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + + out = send("consul:session?consulClient=#consul&action=" + ConsulSessionActions.RENEW, "session-2"); + assertEquals("session-2", out.getMessage().getBody()); + assertEquals(false, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + } + + @Test + public void testGetPreparedQuery() { + StoredQuery query = mock(StoredQuery.class); + when(preparedQueryClient.getPreparedQuery("query-1")).thenReturn(Optional.of(query)); + when(preparedQueryClient.getPreparedQuery("query-2")).thenReturn(Optional.empty()); + + Exchange out = send("consul:preparedQuery?consulClient=#consul&action=" + ConsulPreparedQueryActions.GET, + "query-1"); + assertSame(query, out.getMessage().getBody()); + assertEquals(true, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + + out = send("consul:preparedQuery?consulClient=#consul&action=" + ConsulPreparedQueryActions.GET, "query-2"); + assertEquals("query-2", out.getMessage().getBody()); + assertEquals(false, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + } + + @Test + public void testAgentActions() { + Map<String, Service> services = Map.of("foo", mock(Service.class)); + when(agentClient.getServices()).thenReturn(services); + + Exchange out = send("consul:agent?consulClient=#consul&action=" + ConsulAgentActions.SERVICES, null); + assertSame(services, out.getMessage().getBody()); + assertEquals(true, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + + Registration registration = mock(Registration.class); + out = send("consul:agent?consulClient=#consul&action=" + ConsulAgentActions.REGISTER, registration); + verify(agentClient).register(registration); + assertSame(registration, out.getMessage().getBody()); + assertEquals(true, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + + out = template.request("consul:agent?consulClient=#consul&action=" + ConsulAgentActions.DEREGISTER, + e -> e.getMessage().setHeader(ConsulConstants.CONSUL_SERVICE_ID, "foo-1")); + verify(agentClient).deregister(eq("foo-1"), any()); + assertEquals(true, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + } + + @Test + public void testStatusActions() { + when(statusClient.getLeader()).thenReturn("10.0.0.1:8300"); + when(statusClient.getPeers()).thenReturn(List.of("10.0.0.1:8300")); + + Exchange out = send("consul:status?consulClient=#consul&action=" + ConsulStatusActions.LEADER, null); + assertEquals("10.0.0.1:8300", out.getMessage().getBody()); + assertEquals(true, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + + out = send("consul:status?consulClient=#consul&action=" + ConsulStatusActions.PEERS, null); + assertEquals(List.of("10.0.0.1:8300"), out.getMessage().getBody()); + assertEquals(true, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + } + + @Test + public void testCatalogRegister() { + CatalogRegistration registration = mock(CatalogRegistration.class); + + Exchange out = send("consul:catalog?consulClient=#consul&action=" + ConsulCatalogActions.REGISTER, registration); + + verify(catalogClient).register(registration); + assertEquals(true, out.getMessage().getHeader(ConsulConstants.CONSUL_RESULT)); + } + + private Exchange send(String uri, Object body) { + Exchange out = template.request(uri, e -> e.getMessage().setBody(body)); + if (out.getException() != null) { + throw new AssertionError("The exchange failed", out.getException()); + } + return out; + } +} 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 7c82ebd18930..259b3103e750 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 @@ -828,6 +828,20 @@ receives such a body as a `String`. A `null` body is now published as a JSON `null` that the subscribed clients receive. Before, the message had no data, so the clients took it for the reply of a publish and never received it, while the exchange succeeded. +=== camel-consul - no Optional in the body, and the CamelConsulResult header of every action + +The `GET_SESSIONS` action of `consul:kv`, the `RENEW` action of `consul:session` and the `GET` action of +`consul:preparedQuery` set the `java.util.Optional` of the Consul client as the body, and the `CamelConsulResult` +header was always `true`. The body is now the value itself: the session id as a `String`, the `SessionInfo` or the +`StoredQuery`. When there is none, the body is unchanged and `CamelConsulResult` is `false`, as with the `INFO` action +of `consul:session`. + +The actions of `consul:agent` and `consul:status` now set the `CamelConsulResult` header, as the other actions do. +`REGISTER` and `DEREGISTER` of `consul:catalog` now set it to `true` instead of `false`. + +The `FIRE` action of `consul:event` with a message without body failed with a `NullPointerException`. It now fires the +event without payload. + === camel-aws - the HTTP client is no longer looked up on the classpath The camel-aws components now set the HTTP client of the AWS SDK clients they create for their endpoints: the Apache 5
