blob: 2a2a85a33d5bcf465bdddbe0ad60fc09db68f2d7 [file] [log] [blame]
/*
* 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.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;
import com.google.protobuf.Message;
import com.google.protobuf.Message.Builder;
import org.apache.camel.util.ObjectHelper;
public final class ProtobufConverter {
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();
return convertMapToMessage(descriptor, target, 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");
// we set our values from map to descriptor
inputData.forEach((key, value) -> {
final FieldDescriptor fieldDescriptor = descriptor.findFieldByName(key.toString());
// if we don't find our desired fieldDescriptor, we just ignore it
if (fieldDescriptor != null) {
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'", fieldDescriptor.getName()));
repeatedValues.forEach(repeatedValue -> builder.addRepeatedField(fieldDescriptor, getSuitableFieldValue(fieldDescriptor, builder, repeatedValue)));
} else {
builder.setField(fieldDescriptor, getSuitableFieldValue(fieldDescriptor, builder, value));
}
}
});
return builder.build();
}
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");
switch (fieldDescriptor.getJavaType()) {
case ENUM:
return getEnumValue(fieldDescriptor, inputValue);
case MESSAGE:
final Map<?, ?> nestedValue = castValue(inputValue, Map.class, String.format("Not able to cast value to map, make sure you have a map for the nested field message '%s'", fieldDescriptor.getName()));
// we do a nested call until we reach our final message
return convertMapToMessage(fieldDescriptor.getMessageType(), builder.newBuilderForField(fieldDescriptor), nestedValue);
default:
return inputValue;
}
}
private static EnumValueDescriptor getEnumValue(final FieldDescriptor fieldDescriptor, final Object value) {
final EnumValueDescriptor enumValueDescriptor = getSuitableEnumValue(fieldDescriptor, value);
if (enumValueDescriptor == null) {
throw new IllegalArgumentException(String.format("Could not retrieve enum index '%s' for enum field '%s', most likely the index does not exist in the enum indexes '%s'",
value, fieldDescriptor.getName(), fieldDescriptor.getEnumType().getValues()));
}
return enumValueDescriptor;
}
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);
} else {
final int index = castValue(value, Integer.class, String.format("Not able to cast value to integer, make sure you have an integer index for the enum field '%s'", fieldDescriptor.getName()));
return fieldDescriptor.getEnumType().findValueByNumber(index);
}
}
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);
} catch (ClassCastException e) {
throw new IllegalArgumentException(errorMessage, e);
}
}
}