
com.blueapron.connect.protobuf.ProtobufConverter Maven / Gradle / Ivy
package com.blueapron.connect.protobuf;
import org.apache.kafka.connect.data.Schema;
import org.apache.kafka.connect.data.SchemaAndValue;
import org.apache.kafka.connect.errors.ConnectException;
import org.apache.kafka.connect.storage.Converter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import com.google.protobuf.GeneratedMessageV3;
/**
* Implementation of Converter that uses Protobufs.
*/
public class ProtobufConverter implements Converter {
private static final Logger log = LoggerFactory.getLogger(ProtobufConverter.class);
private static final String CONFIG_PROTO_CLASS_NAME = "protoClassName";
private static final String CONFIG_LEGACY_FIELD_NAME = "legacyName";
private static final String CONFIG_PROTO_MAP_CONVERSION_TYPE = "protoMapConversionType";
private static final String CONFIG_MAP_FIELDS_BY = "mapFieldsBy";
private enum MapBy {
INDEX, NAME
}
private ProtobufData protobufData;
private boolean mapByName;
@Override
public void configure(Map configs, boolean isKey) {
String legacyName = (String) configs.get(CONFIG_LEGACY_FIELD_NAME);
legacyName = legacyName == null ? "legacy_name" : legacyName; //Because you cannot do getOrDefault on a Map
//The possible values for this config-property are not documented
boolean useConnectSchemaMap = "map".equals(configs.get(CONFIG_PROTO_MAP_CONVERSION_TYPE));
String protoClassName = (String) configs.get(CONFIG_PROTO_CLASS_NAME);
if (protoClassName == null) {
if (isKey) {
return;
} else {
throw new ConnectException("Value converter must have a " + CONFIG_PROTO_CLASS_NAME + " configured");
}
}
if (configs.containsKey(CONFIG_MAP_FIELDS_BY)) {
MapBy mapBy = MapBy.valueOf((String) configs.get(CONFIG_MAP_FIELDS_BY));
mapByName = mapBy == MapBy.NAME;
} else {
mapByName = false;
}
try {
log.info("Initializing ProtobufData with args: [protoClassName={}, legacyName={}, useConnectSchemaMap={}]",
protoClassName, legacyName, useConnectSchemaMap);
// We are using dynamic class-loading to have a generic protobuf converter.
// The purpose is to allow having the both this converter and the compiled proto-files on the classpath and having
// the fully-qualified classname as a configuration-value.
// As the class-name comes from the configuration of the Kafka connector, an attacker would need to be able to
// both add a class to the classpath and apply changes to the configuration of a Kafka Connect instance running
// with this connector.
protobufData = new ProtobufData(Class.forName(protoClassName).asSubclass(GeneratedMessageV3.class), legacyName, useConnectSchemaMap);
} catch (ClassNotFoundException e) {
throw new ConnectException("Proto class " + protoClassName + " not found in the classpath");
} catch (ClassCastException e) {
throw new ConnectException("Proto class " + protoClassName + " is not a valid proto3 message class");
}
}
@Override
public byte[] fromConnectData(String topic, Schema schema, Object value) {
if (protobufData == null || schema == null || value == null) {
return null;
}
if (mapByName) {
return protobufData.fromConnectDataByName(value);
} else {
return protobufData.fromConnectData(value);
}
}
@Override
public SchemaAndValue toConnectData(String topic, byte[] value) {
if (protobufData == null || value == null) {
return SchemaAndValue.NULL;
}
return protobufData.toConnectData(value);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy