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 696f8c88aed7 CAMEL-26156: camel-pubnub - FIRE and SETSTATE with an 
empty body fail without calling PubNub, and PNMessageResult converts to String 
(#28928)
696f8c88aed7 is described below

commit 696f8c88aed7868870fa3bdea3d3c5756fd232fb
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 21:47:19 2026 +0100

    CAMEL-26156: camel-pubnub - FIRE and SETSTATE with an empty body fail 
without calling PubNub, and PNMessageResult converts to String (#28928)
    
    * CAMEL-26156: camel-pubnub - FIRE and SETSTATE with an empty body fail…
    
    … without calling PubNub, and PNMessageResult converts to String
    
    FIRE and SETSTATE set an exception and called the callback when the body 
was empty, but
    did not return: the PubNub call still ran and called the callback a second 
time, and
    process returned false. They now throw the CamelException as PUBLISH does, 
so process
    completes the exchange once, synchronously, without calling PubNub.
    
    Add a PubNubConverter so the PNMessageResult body of the consumer converts 
to a String:
    a string message as its text, any other message as JSON.
    
    * CAMEL-26156: camel-pubnub - SETSTATE with an empty body fails with "C…
    
    …an not set state with an empty body"
    
    The SETSTATE failure said "Can not publish empty message", which is the 
wording of a
    publish. It now says "Can not set state with an empty body".
    
    * CAMEL-26156: camel-pubnub - escape attribute references in the docs
    
    Asciidoctor read ${...} in inline code as a document attribute reference, 
which failed the
    PR doc validation. Use the +...+ passthrough so the text shows as written.
    
    * Merge remote-tracking branch 'origin/main' into CAMEL-26156-pubnub-fi…
    
    …re-setstate
    
    
    # Conflicts:
    #       docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/catalog/components/pubnub.json    |  2 +-
 .../camel/catalog/docs/pubnub-component.adoc       |  3 +
 .../component/pubnub/PubNubConverterLoader.java    | 60 ++++++++++++++
 .../org/apache/camel/component/pubnub/pubnub.json  |  2 +-
 .../services/org/apache/camel/TypeConverterLoader  |  2 +
 .../src/main/docs/pubnub-component.adoc            |  3 +
 .../camel/component/pubnub/PubNubConverter.java    | 45 +++++++++++
 .../camel/component/pubnub/PubNubEndpoint.java     |  3 +-
 .../camel/component/pubnub/PubNubProducer.java     | 10 +--
 .../component/pubnub/PubNubConverterTest.java      | 63 +++++++++++++++
 .../component/pubnub/PubNubEmptyBodyTest.java      | 92 ++++++++++++++++++++++
 .../component/pubnub/PubNubSubscriberTest.java     |  3 +
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    | 10 +++
 13 files changed, 289 insertions(+), 9 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pubnub.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pubnub.json
index e4d1181ad220..8f1b477e5a07 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pubnub.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/pubnub.json
@@ -45,7 +45,7 @@
     "CamelPubNubUUID": { "index": 4, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "UUID to be used as a device identifier.", 
"constantName": "org.apache.camel.component.pubnub.PubNubConstants#UUID" }
   },
   "bodyTypes": {
-    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"com.pubnub.api.models.consumer.pubsub.PNMessageResult", "byOption": [ { 
"option": "withPresence", "value": "true", "carriers": [ "pojo" ] } ], 
"description": "The PubNub PNMessageResult; its getMessage() is the payload as 
a Gson JsonElement, and it does not convert to the payload text. With 
withPresence, a presence event comes as a PNPresenceEventResult. The channel 
and timetoken are in the CamelPubNubChannel and CamelPubNubTimeTo [...]
+    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"com.pubnub.api.models.consumer.pubsub.PNMessageResult", "byOption": [ { 
"option": "withPresence", "value": "true", "carriers": [ "pojo" ] } ], 
"description": "The PubNub PNMessageResult; its getMessage() is the payload as 
a Gson JsonElement. It converts to a String: a string message as its text, any 
other message as JSON. With withPresence, a presence event comes as a 
PNPresenceEventResult. The channel and timetoken are in the Cam [...]
     "producerIn": { "carriers": [ "text", "value", "map", "list", "pojo" ], 
"byOption": [ { "option": "operation", "value": "SETSTATE", "carriers": [ 
"map", "pojo" ] }, { "option": "operation", "value": "GETHISTORY", "carriers": 
[ "any" ] }, { "option": "operation", "value": "GETSTATE", "carriers": [ "any" 
] }, { "option": "operation", "value": "HERENOW", "carriers": [ "any" ] } ], 
"description": "The message to publish or fire, serialized to JSON by the 
PubNub client: text is sent as a  [...]
     "producerOut": { "carriers": [ "unchanged" ], "byOption": [ { "option": 
"operation", "value": "GETHISTORY", "carriers": [ "list", "unchanged" ] }, { 
"option": "operation", "value": "GETSTATE", "carriers": [ "pojo" ], "javaType": 
"com.pubnub.api.models.consumer.presence.PNGetStateResult" }, { "option": 
"operation", "value": "HERENOW", "carriers": [ "pojo" ], "javaType": 
"com.pubnub.api.models.consumer.presence.PNHereNowResult" }, { "option": 
"operation", "value": "SETSTATE", "carriers [...]
   },
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/pubnub-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/pubnub-component.adoc
index c99ce1395990..ac299c662bd6 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/pubnub-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/pubnub-component.adoc
@@ -64,6 +64,9 @@ Pass the full object as the message payload.
 PubNub will take care of object serialization.
 
 When receiving the message body uses objects provided by the PubNub API.
+A message is a `PNMessageResult`, and `getMessage()` is the payload as a Gson 
`JsonElement`.
+The `PNMessageResult` converts to a `String`, so `+${body}+` or 
`convertBodyTo(String.class)` gives the payload:
+a string message as its text, and any other message (an object, an array, a 
number) as JSON.
 
 == Examples
 
diff --git 
a/components/camel-pubnub/src/generated/java/org/apache/camel/component/pubnub/PubNubConverterLoader.java
 
b/components/camel-pubnub/src/generated/java/org/apache/camel/component/pubnub/PubNubConverterLoader.java
new file mode 100644
index 000000000000..32df8f30563d
--- /dev/null
+++ 
b/components/camel-pubnub/src/generated/java/org/apache/camel/component/pubnub/PubNubConverterLoader.java
@@ -0,0 +1,60 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.pubnub;
+
+import javax.annotation.processing.Generated;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.DeferredContextBinding;
+import org.apache.camel.Exchange;
+import org.apache.camel.TypeConversionException;
+import org.apache.camel.TypeConverterLoaderException;
+import org.apache.camel.spi.TypeConverterLoader;
+import org.apache.camel.spi.TypeConverterRegistry;
+import org.apache.camel.support.SimpleTypeConverter;
+import org.apache.camel.support.TypeConverterSupport;
+import org.apache.camel.util.DoubleMap;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.TypeConverterLoaderGeneratorMojo")
+@SuppressWarnings("unchecked")
+@DeferredContextBinding
+public final class PubNubConverterLoader implements TypeConverterLoader, 
CamelContextAware {
+
+    private CamelContext camelContext;
+
+    public PubNubConverterLoader() {
+    }
+
+    @Override
+    public void setCamelContext(CamelContext camelContext) {
+        this.camelContext = camelContext;
+    }
+
+    @Override
+    public CamelContext getCamelContext() {
+        return camelContext;
+    }
+
+    @Override
+    public void load(TypeConverterRegistry registry) throws 
TypeConverterLoaderException {
+        registerConverters(registry);
+    }
+
+    private void registerConverters(TypeConverterRegistry registry) {
+        addTypeConverter(registry, java.lang.String.class, 
com.pubnub.api.models.consumer.pubsub.PNMessageResult.class, false,
+            (type, exchange, value) -> {
+                Object answer = 
org.apache.camel.component.pubnub.PubNubConverter.toString((com.pubnub.api.models.consumer.pubsub.PNMessageResult)
 value);
+                if (false && answer == null) {
+                    answer = Void.class;
+                }
+                return answer;
+            });
+    }
+
+    private static void addTypeConverter(TypeConverterRegistry registry, 
Class<?> toType, Class<?> fromType, boolean allowNull, 
SimpleTypeConverter.ConversionMethod method) {
+        registry.addTypeConverter(toType, fromType, new 
SimpleTypeConverter(allowNull, method));
+    }
+}
diff --git 
a/components/camel-pubnub/src/generated/resources/META-INF/org/apache/camel/component/pubnub/pubnub.json
 
b/components/camel-pubnub/src/generated/resources/META-INF/org/apache/camel/component/pubnub/pubnub.json
index e4d1181ad220..8f1b477e5a07 100644
--- 
a/components/camel-pubnub/src/generated/resources/META-INF/org/apache/camel/component/pubnub/pubnub.json
+++ 
b/components/camel-pubnub/src/generated/resources/META-INF/org/apache/camel/component/pubnub/pubnub.json
@@ -45,7 +45,7 @@
     "CamelPubNubUUID": { "index": 4, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "UUID to be used as a device identifier.", 
"constantName": "org.apache.camel.component.pubnub.PubNubConstants#UUID" }
   },
   "bodyTypes": {
-    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"com.pubnub.api.models.consumer.pubsub.PNMessageResult", "byOption": [ { 
"option": "withPresence", "value": "true", "carriers": [ "pojo" ] } ], 
"description": "The PubNub PNMessageResult; its getMessage() is the payload as 
a Gson JsonElement, and it does not convert to the payload text. With 
withPresence, a presence event comes as a PNPresenceEventResult. The channel 
and timetoken are in the CamelPubNubChannel and CamelPubNubTimeTo [...]
+    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"com.pubnub.api.models.consumer.pubsub.PNMessageResult", "byOption": [ { 
"option": "withPresence", "value": "true", "carriers": [ "pojo" ] } ], 
"description": "The PubNub PNMessageResult; its getMessage() is the payload as 
a Gson JsonElement. It converts to a String: a string message as its text, any 
other message as JSON. With withPresence, a presence event comes as a 
PNPresenceEventResult. The channel and timetoken are in the Cam [...]
     "producerIn": { "carriers": [ "text", "value", "map", "list", "pojo" ], 
"byOption": [ { "option": "operation", "value": "SETSTATE", "carriers": [ 
"map", "pojo" ] }, { "option": "operation", "value": "GETHISTORY", "carriers": 
[ "any" ] }, { "option": "operation", "value": "GETSTATE", "carriers": [ "any" 
] }, { "option": "operation", "value": "HERENOW", "carriers": [ "any" ] } ], 
"description": "The message to publish or fire, serialized to JSON by the 
PubNub client: text is sent as a  [...]
     "producerOut": { "carriers": [ "unchanged" ], "byOption": [ { "option": 
"operation", "value": "GETHISTORY", "carriers": [ "list", "unchanged" ] }, { 
"option": "operation", "value": "GETSTATE", "carriers": [ "pojo" ], "javaType": 
"com.pubnub.api.models.consumer.presence.PNGetStateResult" }, { "option": 
"operation", "value": "HERENOW", "carriers": [ "pojo" ], "javaType": 
"com.pubnub.api.models.consumer.presence.PNHereNowResult" }, { "option": 
"operation", "value": "SETSTATE", "carriers [...]
   },
diff --git 
a/components/camel-pubnub/src/generated/resources/META-INF/services/org/apache/camel/TypeConverterLoader
 
b/components/camel-pubnub/src/generated/resources/META-INF/services/org/apache/camel/TypeConverterLoader
new file mode 100644
index 000000000000..851225edb141
--- /dev/null
+++ 
b/components/camel-pubnub/src/generated/resources/META-INF/services/org/apache/camel/TypeConverterLoader
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+org.apache.camel.component.pubnub.PubNubConverterLoader
diff --git a/components/camel-pubnub/src/main/docs/pubnub-component.adoc 
b/components/camel-pubnub/src/main/docs/pubnub-component.adoc
index c99ce1395990..ac299c662bd6 100644
--- a/components/camel-pubnub/src/main/docs/pubnub-component.adoc
+++ b/components/camel-pubnub/src/main/docs/pubnub-component.adoc
@@ -64,6 +64,9 @@ Pass the full object as the message payload.
 PubNub will take care of object serialization.
 
 When receiving the message body uses objects provided by the PubNub API.
+A message is a `PNMessageResult`, and `getMessage()` is the payload as a Gson 
`JsonElement`.
+The `PNMessageResult` converts to a `String`, so `+${body}+` or 
`convertBodyTo(String.class)` gives the payload:
+a string message as its text, and any other message (an object, an array, a 
number) as JSON.
 
 == Examples
 
diff --git 
a/components/camel-pubnub/src/main/java/org/apache/camel/component/pubnub/PubNubConverter.java
 
b/components/camel-pubnub/src/main/java/org/apache/camel/component/pubnub/PubNubConverter.java
new file mode 100644
index 000000000000..bdd908389c5f
--- /dev/null
+++ 
b/components/camel-pubnub/src/main/java/org/apache/camel/component/pubnub/PubNubConverter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.pubnub;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.pubnub.api.models.consumer.pubsub.PNMessageResult;
+import org.apache.camel.Converter;
+
+/**
+ * Converts the PubNub objects the consumer sets as the message body.
+ */
+@Converter(generateLoader = true)
+public final class PubNubConverter {
+
+    private PubNubConverter() {
+    }
+
+    /**
+     * The payload of a received PubNub message as text: a string message as 
its text, any other message (a JSON object,
+     * array, number or boolean) as JSON.
+     */
+    @Converter
+    public static String toString(PNMessageResult message) {
+        JsonElement payload = message.getMessage();
+        if (payload instanceof JsonPrimitive primitive && 
primitive.isString()) {
+            return primitive.getAsString();
+        }
+        return payload.toString();
+    }
+}
diff --git 
a/components/camel-pubnub/src/main/java/org/apache/camel/component/pubnub/PubNubEndpoint.java
 
b/components/camel-pubnub/src/main/java/org/apache/camel/component/pubnub/PubNubEndpoint.java
index 24e56b3438ad..d8ab8417d30f 100644
--- 
a/components/camel-pubnub/src/main/java/org/apache/camel/component/pubnub/PubNubEndpoint.java
+++ 
b/components/camel-pubnub/src/main/java/org/apache/camel/component/pubnub/PubNubEndpoint.java
@@ -42,7 +42,8 @@ import org.apache.camel.support.DefaultEndpoint;
                                    byOption = @BodyType.When(option = 
"withPresence", value = "true",
                                                              carriers = 
Carrier.POJO),
                                    description = "The PubNub PNMessageResult; 
its getMessage() is the payload as a Gson"
-                                                 + " JsonElement, and it does 
not convert to the payload text. With"
+                                                 + " JsonElement. It converts 
to a String: a string message as its text,"
+                                                 + " any other message as 
JSON. With"
                                                  + " withPresence, a presence 
event comes as a PNPresenceEventResult. The"
                                                  + " channel and timetoken are 
in the CamelPubNubChannel and"
                                                  + " CamelPubNubTimeToken 
headers"),
diff --git 
a/components/camel-pubnub/src/main/java/org/apache/camel/component/pubnub/PubNubProducer.java
 
b/components/camel-pubnub/src/main/java/org/apache/camel/component/pubnub/PubNubProducer.java
index 54c0cb402169..0a65abda1d00 100644
--- 
a/components/camel-pubnub/src/main/java/org/apache/camel/component/pubnub/PubNubProducer.java
+++ 
b/components/camel-pubnub/src/main/java/org/apache/camel/component/pubnub/PubNubProducer.java
@@ -123,11 +123,10 @@ public class PubNubProducer extends DefaultAsyncProducer {
                 });
     }
 
-    private void doFire(Exchange exchange, AsyncCallback callback) {
+    private void doFire(Exchange exchange, AsyncCallback callback) throws 
CamelException {
         Object body = exchange.getIn().getBody();
         if (ObjectHelper.isEmpty(body)) {
-            exchange.setException(new CamelException("Can not fire empty 
message"));
-            callback.done(true);
+            throw new CamelException("Can not fire empty message");
         }
         LOG.debug("Sending message [{}] to channel [{}]", body, 
getChannel(exchange));
         endpoint.getPubnub()
@@ -171,11 +170,10 @@ public class PubNubProducer extends DefaultAsyncProducer {
                 });
     }
 
-    private void doSetState(Exchange exchange, AsyncCallback callback) {
+    private void doSetState(Exchange exchange, AsyncCallback callback) throws 
CamelException {
         Object body = exchange.getIn().getBody();
         if (ObjectHelper.isEmpty(body)) {
-            exchange.setException(new CamelException("Can not publish empty 
message"));
-            callback.done(true);
+            throw new CamelException("Can not set state with an empty body");
         }
         LOG.debug("Sending setState [{}] to channel [{}]", body, 
getChannel(exchange));
         endpoint.getPubnub()
diff --git 
a/components/camel-pubnub/src/test/java/org/apache/camel/component/pubnub/PubNubConverterTest.java
 
b/components/camel-pubnub/src/test/java/org/apache/camel/component/pubnub/PubNubConverterTest.java
new file mode 100644
index 000000000000..1f898f5e2bc0
--- /dev/null
+++ 
b/components/camel-pubnub/src/test/java/org/apache/camel/component/pubnub/PubNubConverterTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.pubnub;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.pubnub.api.models.consumer.pubsub.BasePubSubResult;
+import com.pubnub.api.models.consumer.pubsub.PNMessageResult;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class PubNubConverterTest extends CamelTestSupport {
+
+    @Test
+    public void testStringMessageToString() {
+        assertEquals("Hello World", convert(new JsonPrimitive("Hello World")));
+    }
+
+    @Test
+    public void testObjectMessageToString() {
+        JsonObject json = new JsonObject();
+        json.addProperty("text", "Message");
+        json.addProperty("count", 3);
+        assertEquals("{\"text\":\"Message\",\"count\":3}", convert(json));
+    }
+
+    @Test
+    public void testArrayMessageToString() {
+        JsonArray json = new JsonArray();
+        json.add("a");
+        json.add(1);
+        assertEquals("[\"a\",1]", convert(json));
+    }
+
+    @Test
+    public void testNumberMessageToString() {
+        assertEquals("42", convert(new JsonPrimitive(42)));
+    }
+
+    private String convert(JsonElement payload) {
+        PNMessageResult message = new PNMessageResult(
+                new BasePubSubResult("mychannel", null, 14607577960932487L, 
null, "publisher"), payload, null, null);
+        return context.getTypeConverter().convertTo(String.class, message);
+    }
+}
diff --git 
a/components/camel-pubnub/src/test/java/org/apache/camel/component/pubnub/PubNubEmptyBodyTest.java
 
b/components/camel-pubnub/src/test/java/org/apache/camel/component/pubnub/PubNubEmptyBodyTest.java
new file mode 100644
index 000000000000..1085a09aa288
--- /dev/null
+++ 
b/components/camel-pubnub/src/test/java/org/apache/camel/component/pubnub/PubNubEmptyBodyTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.pubnub;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.camel.AsyncProducer;
+import org.apache.camel.CamelException;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;
+import static com.github.tomakehurst.wiremock.client.WireMock.verify;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * FIRE and SETSTATE with an empty body fail without calling PubNub, like 
PUBLISH.
+ */
+public class PubNubEmptyBodyTest extends PubNubTestBase {
+
+    @Test
+    public void testFireNullBody() {
+        Exchange exchange = template.request("direct:fire", e -> 
e.getIn().setBody(null));
+
+        CamelException cause = assertInstanceOf(CamelException.class, 
exchange.getException());
+        assertEquals("Can not fire empty message", cause.getMessage());
+        verify(0, anyRequestedFor(anyUrl()));
+    }
+
+    @Test
+    public void testSetStateEmptyBody() {
+        Exchange exchange = template.request("direct:setstate", e -> 
e.getIn().setBody(Map.of()));
+
+        CamelException cause = assertInstanceOf(CamelException.class, 
exchange.getException());
+        assertEquals("Can not set state with an empty body", 
cause.getMessage());
+        verify(0, anyRequestedFor(anyUrl()));
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = { "FIRE", "SETSTATE" })
+    public void testEmptyBodyCompletesSynchronouslyOnce(String operation) 
throws Exception {
+        AsyncProducer producer = 
context.getEndpoint("pubnub:someChannel?uuid=myuuid&pubnub=#pubnub").createAsyncProducer();
+        producer.start();
+        try {
+            Exchange exchange = new DefaultExchange(context);
+            exchange.getIn().setHeader(PubNubConstants.OPERATION, operation);
+            AtomicInteger doneSync = new AtomicInteger();
+            AtomicInteger doneAsync = new AtomicInteger();
+
+            boolean sync = producer.process(exchange, ds -> (ds ? doneSync : 
doneAsync).incrementAndGet());
+
+            assertTrue(sync, "process must return true as the callback was 
called synchronously");
+            assertEquals(1, doneSync.get());
+            assertEquals(0, doneAsync.get());
+            assertInstanceOf(CamelException.class, exchange.getException());
+        } finally {
+            producer.stop();
+        }
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                
from("direct:fire").to("pubnub:someChannel?operation=fire&pubnub=#pubnub");
+                
from("direct:setstate").to("pubnub:someChannel?operation=setstate&uuid=myuuid&pubnub=#pubnub");
+            }
+        };
+    }
+}
diff --git 
a/components/camel-pubnub/src/test/java/org/apache/camel/component/pubnub/PubNubSubscriberTest.java
 
b/components/camel-pubnub/src/test/java/org/apache/camel/component/pubnub/PubNubSubscriberTest.java
index 5caf4c9cdda8..382d9b82b572 100644
--- 
a/components/camel-pubnub/src/test/java/org/apache/camel/component/pubnub/PubNubSubscriberTest.java
+++ 
b/components/camel-pubnub/src/test/java/org/apache/camel/component/pubnub/PubNubSubscriberTest.java
@@ -25,6 +25,7 @@ import static 
com.github.tomakehurst.wiremock.client.WireMock.aResponse;
 import static com.github.tomakehurst.wiremock.client.WireMock.get;
 import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
 import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class PubNubSubscriberTest extends PubNubTestBase {
 
@@ -45,6 +46,8 @@ public class PubNubSubscriberTest extends PubNubTestBase {
         mockResult.expectedMessageCount(1);
         mockResult.expectedHeaderReceived(PubNubConstants.CHANNEL, 
"mychannel");
         MockEndpoint.assertIsSatisfied(context);
+        // the PNMessageResult body converts to the JSON of the message
+        assertEquals("{\"text\":\"Message\"}", 
mockResult.getReceivedExchanges().get(0).getIn().getBody(String.class));
     }
 
     @Override
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 c6c2feb52962..051f557f6290 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
@@ -1049,6 +1049,16 @@ changed fields of an update are now in the new 
`CamelDebeziumUpdateDescription`
 
 The `Struct` to `Map` type converter now also converts a `Struct` inside a 
list or a map.
 
+=== camel-pubnub - FIRE and SETSTATE with an empty body fail without calling 
PubNub
+
+The `FIRE` and `SETSTATE` operations with an empty body now fail with a 
`CamelException` (`Can not fire empty
+message` or `Can not set state with an empty body`) and do not call PubNub, as 
`PUBLISH` does. Before, the call to
+PubNub still ran, so `FIRE` with a null body could end with the 
`PubNubException` `Message Missing` instead. The
+message of the `SETSTATE` failure was `Can not publish empty message`.
+
+The `PNMessageResult` body of the consumer now converts to a `String`: a 
string message as its text, any other message
+as JSON. Before, the conversion gave the `toString()` of the `PNMessageResult` 
object.
+
 === camel-jbang - camel validate normalize writes one file per input file to 
an output directory
 
 `camel validate normalize <files> --output=<dir>` now writes one normalized 
file per input file into the

Reply via email to