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 278254ec258e CAMEL-25699: camel-protobuf - Keep the content of bytes 
fields when converting a message to and from a Map (#27984)
278254ec258e is described below

commit 278254ec258e6b3cd5cdaa38725242ed92ef3b12
Author: allthingssecurity <[email protected]>
AuthorDate: Sat Oct 17 14:43:34 2026 +0530

    CAMEL-25699: camel-protobuf - Keep the content of bytes fields when 
converting a message to and from a Map (#27984)
    
    ProtobufConverter turned a bytes field into ByteString.toString() when a
    message was converted to a Map. That is a description for logging (identity
    hash, size and a shortened, escaped preview), not the value, so the content
    of every bytes field was lost without an error. In the other direction a
    byte[] for a bytes field was passed to Builder.setField unchanged and
    rejected by protobuf.
    
    A bytes field now becomes a byte[] in the Map, and a byte[] in a Map is
    wrapped with ByteString.copyFrom when the Map is converted to a message.
    Enum fields are unchanged. The 4.24 upgrade guide notes the new value type.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../camel/catalog/docs/protobuf-dataformat.adoc    |  2 +
 .../src/main/docs/protobuf-dataformat.adoc         |  2 +
 .../dataformat/protobuf/ProtobufConverter.java     | 10 ++-
 .../protobuf/ProtobufBytesFieldMapTest.java        | 82 ++++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  7 ++
 5 files changed, 102 insertions(+), 1 deletion(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/protobuf-dataformat.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/protobuf-dataformat.adoc
index a5c5319d4b93..210acbe359f4 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/protobuf-dataformat.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/protobuf-dataformat.adoc
@@ -63,6 +63,8 @@ data from proto `Message` to `Map`,
 you will need to set the `contentTypeFormat` to `native`
 and explicitly define the data type `Map` when you get body of the exchange.
 For instance: `exchange.getMessage().getBody(Map.class)`.
+In the `Map`, the value of a `bytes` field is a `byte[]`; when a `Map` is 
converted to a message, a `bytes` field takes a
+`byte[]` (or a `ByteString`).
 
 == Protobuf overview
 
diff --git a/components/camel-protobuf/src/main/docs/protobuf-dataformat.adoc 
b/components/camel-protobuf/src/main/docs/protobuf-dataformat.adoc
index a5c5319d4b93..210acbe359f4 100644
--- a/components/camel-protobuf/src/main/docs/protobuf-dataformat.adoc
+++ b/components/camel-protobuf/src/main/docs/protobuf-dataformat.adoc
@@ -63,6 +63,8 @@ data from proto `Message` to `Map`,
 you will need to set the `contentTypeFormat` to `native`
 and explicitly define the data type `Map` when you get body of the exchange.
 For instance: `exchange.getMessage().getBody(Map.class)`.
+In the `Map`, the value of a `bytes` field is a `byte[]`; when a `Map` is 
converted to a message, a `bytes` field takes a
+`byte[]` (or a `ByteString`).
 
 == Protobuf overview
 
diff --git 
a/components/camel-protobuf/src/main/java/org/apache/camel/dataformat/protobuf/ProtobufConverter.java
 
b/components/camel-protobuf/src/main/java/org/apache/camel/dataformat/protobuf/ProtobufConverter.java
index a6c62b76611d..8d41bfa8d45a 100644
--- 
a/components/camel-protobuf/src/main/java/org/apache/camel/dataformat/protobuf/ProtobufConverter.java
+++ 
b/components/camel-protobuf/src/main/java/org/apache/camel/dataformat/protobuf/ProtobufConverter.java
@@ -21,6 +21,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
+import com.google.protobuf.ByteString;
 import com.google.protobuf.Descriptors;
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.EnumValueDescriptor;
@@ -88,6 +89,10 @@ public final class ProtobufConverter {
                 return convertMapToMessage(fieldDescriptor.getMessageType(), 
builder.newBuilderForField(fieldDescriptor),
                         nestedValue);
 
+            case BYTE_STRING:
+                // a bytes field is given as byte[] in the map (see 
convertValueToSuitableFieldType)
+                return inputValue instanceof byte[] bytes ? 
ByteString.copyFrom(bytes) : inputValue;
+
             default:
                 return inputValue;
         }
@@ -157,9 +162,12 @@ public final class ProtobufConverter {
 
         switch (fieldDescriptor.getJavaType()) {
             case ENUM:
-            case BYTE_STRING:
                 result = value.toString();
                 break;
+            case BYTE_STRING:
+                // ByteString.toString() only describes the value (size and a 
shortened text)
+                result = ((ByteString) value).toByteArray();
+                break;
             case MESSAGE:
                 result = convertProtoMessageToMap((Message) value);
                 break;
diff --git 
a/components/camel-protobuf/src/test/java/org/apache/camel/dataformat/protobuf/ProtobufBytesFieldMapTest.java
 
b/components/camel-protobuf/src/test/java/org/apache/camel/dataformat/protobuf/ProtobufBytesFieldMapTest.java
new file mode 100644
index 000000000000..59e21e0fdefa
--- /dev/null
+++ 
b/components/camel-protobuf/src/test/java/org/apache/camel/dataformat/protobuf/ProtobufBytesFieldMapTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.dataformat.protobuf;
+
+import java.util.Map;
+
+import com.google.protobuf.ByteString;
+import com.google.protobuf.BytesValue;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * A {@code bytes} field must keep its value when a message is converted to a 
{@link Map} and back. The well-known type
+ * {@link BytesValue} has a single {@code bytes} field named {@code value}.
+ */
+public class ProtobufBytesFieldMapTest extends CamelTestSupport {
+
+    // not valid UTF-8, and longer than the text that ByteString.toString() 
shows
+    private static final byte[] DATA = new byte[] {
+            0, 1, 2, (byte) 0xff, (byte) 0xfe, 'a', 'b', 'c', 'd', 'e', 'f', 
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+            'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', 
'2', '3', '4', '5', '6', '7', '8', '9', '0',
+            '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', 
'4', '5', '6', '7', '8', '9' };
+
+    @Test
+    public void testToMapKeepsTheBytes() {
+        Map<String, Object> map = 
ProtobufConverter.toMap(BytesValue.of(ByteString.copyFrom(DATA)));
+
+        assertArrayEquals(DATA, (byte[]) map.get("value"));
+    }
+
+    @Test
+    public void testToProtoAcceptsTheBytesOfToMap() {
+        BytesValue original = BytesValue.of(ByteString.copyFrom(DATA));
+
+        BytesValue back = (BytesValue) 
ProtobufConverter.toProto(ProtobufConverter.toMap(original),
+                BytesValue.getDefaultInstance());
+
+        assertEquals(original, back);
+    }
+
+    @Test
+    public void testUnmarshalToMapKeepsTheBytes() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedMessageCount(1);
+
+        template.sendBody("direct:unmarshal", 
BytesValue.of(ByteString.copyFrom(DATA)).toByteArray());
+
+        mock.assertIsSatisfied();
+        Map<?, ?> map = 
mock.getReceivedExchanges().get(0).getMessage().getBody(Map.class);
+        assertArrayEquals(DATA, (byte[]) map.get("value"));
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:unmarshal").unmarshal(new 
ProtobufDataFormat(BytesValue.getDefaultInstance()))
+                        .to("mock:result");
+            }
+        };
+    }
+}
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 84afa10e507b..ff8694f11a3f 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
@@ -1474,3 +1474,10 @@ The consumer now sets the `CamelMiloNodeId` header on 
every exchange, including
 Stopping a consumer now deletes its monitored items. Previously they stayed on 
the server while another endpoint kept
 the shared connection open, so a restarted consumer received each value twice. 
While the server is unreachable, the
 stop waits until the client's next attempt to reconnect has failed.
+
+=== camel-protobuf - a bytes field in a Map
+
+When a protobuf message is converted to a `Map` (for example 
`getBody(Map.class)` after unmarshalling), the value of
+a `bytes` field is now a `byte[]` with the content of the field. Previously it 
was the `String` that
+`ByteString.toString()` returns, a description such as `<ByteString@1b6d3586 
size=4 contents="...">`, which does not
+contain the whole value. When a `Map` is marshalled, a `byte[]` value is 
accepted for a `bytes` field.

Reply via email to