CAMEL-14192: Add toMap in the protobuf data format
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 a4d2acf..2a2a85a 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
@@ -16,9 +16,12 @@
  */
 package org.apache.camel.dataformat.protobuf;
 
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
+import com.google.protobuf.Descriptors;
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.EnumValueDescriptor;
 import com.google.protobuf.Descriptors.FieldDescriptor;
@@ -28,20 +31,9 @@
 
 public final class ProtobufConverter {
 
-    private final Message defaultInstance;
-
-    private ProtobufConverter(final Message defaultInstance) {
-        this.defaultInstance = defaultInstance;
-    }
-
-    public static ProtobufConverter create(final Message defaultInstance) {
-        ObjectHelper.notNull(defaultInstance, "defaultInstance");
-
-        return new ProtobufConverter(defaultInstance);
-    }
-
-    public Message toProto(final Map<?, ?> inputData) {
+    public static Message toProto(final Map<?, ?> inputData, final Message defaultInstance) {
         ObjectHelper.notNull(inputData, "inputData");
+        ObjectHelper.notNull(defaultInstance, "defaultInstance");
 
         final Descriptor descriptor = defaultInstance.getDescriptorForType();
         final Builder target = defaultInstance.newBuilderForType();
@@ -49,7 +41,7 @@
         return convertMapToMessage(descriptor, target, inputData);
     }
 
-    private Message convertMapToMessage(final Descriptor descriptor, final Builder builder, final Map<?, ?> inputData) {
+    private static Message convertMapToMessage(final Descriptor descriptor, final Builder builder, final Map<?, ?> inputData) {
         ObjectHelper.notNull(descriptor, "descriptor");
         ObjectHelper.notNull(builder, "builder");
         ObjectHelper.notNull(inputData, "inputData");
@@ -70,7 +62,7 @@
         return builder.build();
     }
 
-    private Object getSuitableFieldValue(final FieldDescriptor fieldDescriptor, final Builder builder, final Object inputValue) {
+    private static Object getSuitableFieldValue(final FieldDescriptor fieldDescriptor, final Builder builder, final Object inputValue) {
         ObjectHelper.notNull(fieldDescriptor, "fieldDescriptor");
         ObjectHelper.notNull(builder, "builder");
         ObjectHelper.notNull(inputValue, "inputValue");
@@ -89,7 +81,7 @@
         }
     }
 
-    private EnumValueDescriptor getEnumValue(final FieldDescriptor fieldDescriptor, final Object value) {
+    private static EnumValueDescriptor getEnumValue(final FieldDescriptor fieldDescriptor, final Object value) {
         final EnumValueDescriptor enumValueDescriptor = getSuitableEnumValue(fieldDescriptor, value);
 
         if (enumValueDescriptor == null) {
@@ -100,7 +92,7 @@
         return enumValueDescriptor;
     }
 
-    private EnumValueDescriptor getSuitableEnumValue(final FieldDescriptor fieldDescriptor, final Object value) {
+    private static EnumValueDescriptor getSuitableEnumValue(final FieldDescriptor fieldDescriptor, final Object value) {
         // we check if value is string, we find index by name, otherwise by integer
         if (value instanceof String) {
             return fieldDescriptor.getEnumType().findValueByName((String) value);
@@ -110,6 +102,53 @@
         }
     }
 
+    public static Map<String, Object> toMap(final Message inputProto) {
+        return convertProtoMessageToMap(inputProto);
+    }
+
+    private static Map<String, Object> convertProtoMessageToMap(final Message inputData) {
+        ObjectHelper.notNull(inputData, "inputData");
+
+        final Map<Descriptors.FieldDescriptor, Object> allFields = inputData.getAllFields();
+
+        final Map<String, Object> mapResult = new LinkedHashMap<>();
+
+        // we set our values from descriptors to map
+        allFields.forEach((fieldDescriptor, value) -> {
+            final String fieldName = fieldDescriptor.getName();
+            if (fieldDescriptor.isRepeated()) {
+                final List<?> repeatedValues = castValue(value, List.class, String.format("Not able to cast value to list, make sure you have a list for the repeated field '%s'", fieldName));
+                mapResult.put(fieldName, repeatedValues.stream().map(singleValue -> convertValueToSuitableFieldType(singleValue, fieldDescriptor)).collect(Collectors.toList()));
+            } else {
+                mapResult.put(fieldName, convertValueToSuitableFieldType(value, fieldDescriptor));
+            }
+        });
+
+        return mapResult;
+    }
+
+    private static Object convertValueToSuitableFieldType(final Object value, final Descriptors.FieldDescriptor fieldDescriptor) {
+        ObjectHelper.notNull(fieldDescriptor, "fieldDescriptor");
+        ObjectHelper.notNull(value, "value");
+
+        Object result;
+
+        switch (fieldDescriptor.getJavaType()) {
+            case ENUM:
+            case BYTE_STRING:
+                result = value.toString();
+                break;
+            case MESSAGE:
+                result = convertProtoMessageToMap((Message)value);
+                break;
+            default:
+                result = value;
+                break;
+        }
+
+        return result;
+    }
+
     private static <T> T castValue(final Object value, final Class<T> type, final String errorMessage) {
         try {
             return type.cast(value);
diff --git a/components/camel-protobuf/src/main/java/org/apache/camel/dataformat/protobuf/ProtobufDataFormat.java b/components/camel-protobuf/src/main/java/org/apache/camel/dataformat/protobuf/ProtobufDataFormat.java
index b14b28e..ad8fe04 100644
--- a/components/camel-protobuf/src/main/java/org/apache/camel/dataformat/protobuf/ProtobufDataFormat.java
+++ b/components/camel-protobuf/src/main/java/org/apache/camel/dataformat/protobuf/ProtobufDataFormat.java
@@ -147,8 +147,8 @@
     private Message convertGraphToMessage(final Exchange exchange, final Object inputData) throws NoTypeConversionAvailableException {
         final Map<?, ?> messageInMap = exchange.getContext().getTypeConverter().tryConvertTo(Map.class, exchange, inputData);
         if (messageInMap != null) {
-            final ProtobufConverter protobufConverter = ProtobufConverter.create(defaultInstance);
-            return protobufConverter.toProto(messageInMap);
+            //final ProtobufConverter protobufConverter = ProtobufConverter.create(defaultInstance);
+            return ProtobufConverter.toProto(messageInMap, defaultInstance);
         }
         return exchange.getContext().getTypeConverter().mandatoryConvertTo(Message.class, exchange, inputData);
     }
diff --git a/components/camel-protobuf/src/main/java/org/apache/camel/dataformat/protobuf/ProtobufTypeConverter.java b/components/camel-protobuf/src/main/java/org/apache/camel/dataformat/protobuf/ProtobufTypeConverter.java
new file mode 100644
index 0000000..832ec20
--- /dev/null
+++ b/components/camel-protobuf/src/main/java/org/apache/camel/dataformat/protobuf/ProtobufTypeConverter.java
@@ -0,0 +1,15 @@
+package org.apache.camel.dataformat.protobuf;
+
+import java.util.Map;
+
+import com.google.protobuf.Message;
+import org.apache.camel.Converter;
+
+//@Converter(generateLoader = true)
+public class ProtobufTypeConverter {
+
+    //@Converter
+    public static Map<String, Object> toMap(final Message message) {
+        return ProtobufConverter.toMap(message);
+    }
+}
diff --git a/components/camel-protobuf/src/test/java/org/apache/camel/dataformat/protobuf/ProtobufConverterTest.java b/components/camel-protobuf/src/test/java/org/apache/camel/dataformat/protobuf/ProtobufConverterTest.java
index 94ac420..1af84df 100644
--- a/components/camel-protobuf/src/test/java/org/apache/camel/dataformat/protobuf/ProtobufConverterTest.java
+++ b/components/camel-protobuf/src/test/java/org/apache/camel/dataformat/protobuf/ProtobufConverterTest.java
@@ -51,8 +51,7 @@
         input.put("nicknames", Arrays.asList("awesome1", "awesome2"));
         input.put("address", address);
 
-        final ProtobufConverter protobufConverter = ProtobufConverter.create(AddressBookProtos.Person.getDefaultInstance());
-        final AddressBookProtos.Person message = (AddressBookProtos.Person) protobufConverter.toProto(input);
+        final AddressBookProtos.Person message = (AddressBookProtos.Person) ProtobufConverter.toProto(input, AddressBookProtos.Person.getDefaultInstance());
 
         // assert primitives types and strings
         assertEquals("Martin", message.getName());
@@ -82,8 +81,7 @@
         input.put("id", 1234);
         input.put("address", "wrong address");
 
-        final ProtobufConverter protobufConverter = ProtobufConverter.create(AddressBookProtos.Person.getDefaultInstance());
-        final AddressBookProtos.Person message = (AddressBookProtos.Person) protobufConverter.toProto(input);
+        final AddressBookProtos.Person message = (AddressBookProtos.Person) ProtobufConverter.toProto(input, AddressBookProtos.Person.getDefaultInstance());
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -94,8 +92,39 @@
         input.put("id", 1234);
         input.put("nicknames", "wrong nickname");
 
-        final ProtobufConverter protobufConverter = ProtobufConverter.create(AddressBookProtos.Person.getDefaultInstance());
-        final AddressBookProtos.Person message = (AddressBookProtos.Person) protobufConverter.toProto(input);
+        final AddressBookProtos.Person message = (AddressBookProtos.Person) ProtobufConverter.toProto(input, AddressBookProtos.Person.getDefaultInstance());
+    }
+
+    @Test
+    public void testIfItCorrectlyConvertMessageToMap() {
+        final Map<String, Object> phoneNumber = new HashMap<>();
+        phoneNumber.put("number", "011122233");
+        phoneNumber.put("type", "MOBILE");
+
+        final Map<String, Object> phoneNumber2 = new HashMap<>();
+        phoneNumber2.put("number", "5542454");
+        phoneNumber2.put("type", "WORK");
+
+        final Map<String, Object> address = new HashMap<>();
+        address.put("street", "awesome street");
+        address.put("street_number", 12);
+        address.put("is_valid", false);
+
+        final Map<String, Object> input = new HashMap<>();
+
+        input.put("name", "Martin");
+        input.put("id", 1234);
+        input.put("phone", Arrays.asList(phoneNumber, phoneNumber2));
+        input.put("email", "some@some.com");
+        input.put("nicknames", Arrays.asList("awesome1", "awesome2"));
+        input.put("address", address);
+
+        final AddressBookProtos.Person message = (AddressBookProtos.Person) ProtobufConverter.toProto(input, AddressBookProtos.Person.getDefaultInstance());
+
+        final Map<String, Object> resultedMessageMap = ProtobufConverter.toMap(message);
+
+        assertEquals(input, resultedMessageMap);
+
     }
 
 }
diff --git a/components/camel-protobuf/src/test/java/org/apache/camel/dataformat/protobuf/ProtobufMarshalAndUnmarshalMapTest.java b/components/camel-protobuf/src/test/java/org/apache/camel/dataformat/protobuf/ProtobufMarshalAndUnmarshalMapTest.java
index a4c4eb3..201a5d6 100644
--- a/components/camel-protobuf/src/test/java/org/apache/camel/dataformat/protobuf/ProtobufMarshalAndUnmarshalMapTest.java
+++ b/components/camel-protobuf/src/test/java/org/apache/camel/dataformat/protobuf/ProtobufMarshalAndUnmarshalMapTest.java
@@ -42,7 +42,7 @@
         final Map<String, Object> input = new HashMap<>();
         final Map<String, Object> phoneNumber = new HashMap<>();
         phoneNumber.put("number", "011122233");
-        phoneNumber.put("type", 0);
+        phoneNumber.put("type", "MOBILE");
 
         input.put("name", "Martin");
         input.put("id", 1234);
@@ -63,6 +63,9 @@
         assertEquals(1234, output.getId());
         assertEquals("011122233", output.getPhone(0).getNumber());
         assertEquals(0, output.getPhone(0).getType().getNumber());
+
+        final Map resultedMap = mock.getReceivedExchanges().get(0).getMessage().getBody(Map.class);
+        assertEquals(input, resultedMap);
     }
 
     @Override