/* * Copyright 2017 The Hyve and King's College London * * Licensed 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.radarcns.connect.mongodb; import org.apache.kafka.common.config.ConfigDef; import org.apache.kafka.common.config.ConfigDef.Type; import org.apache.kafka.common.config.ConfigException; import org.apache.kafka.common.config.ConfigValue; import org.apache.kafka.common.utils.AppInfoParser; import org.apache.kafka.connect.connector.Task; import org.apache.kafka.connect.sink.SinkConnector; import org.radarcns.connect.mongodb.serialization.RecordConverterFactory; import org.radarcns.connect.util.NotEmptyString; import org.radarcns.connect.util.Utility; import org.radarcns.connect.util.ValidClass; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.apache.kafka.common.config.ConfigDef.Importance.HIGH; import static org.apache.kafka.common.config.ConfigDef.Importance.LOW; import static org.apache.kafka.common.config.ConfigDef.Importance.MEDIUM; import static org.apache.kafka.common.config.ConfigDef.NO_DEFAULT_VALUE; /** * Configures the connection between Kafka and MongoDB. */ public class MongoDbSinkConnector extends SinkConnector { private static final Logger logger = LoggerFactory.getLogger(MongoDbSinkConnector.class); public static final String MONGO_GROUP = "MongoDB"; public static final String MONGO_HOST = "mongo.host"; public static final String MONGO_PORT = "mongo.port"; public static final int MONGO_PORT_DEFAULT = 27017; public static final String MONGO_USERNAME = "mongo.username"; public static final String MONGO_PASSWORD = "mongo.password"; public static final String MONGO_DATABASE = "mongo.database"; public static final String BUFFER_CAPACITY = "buffer.capacity"; public static final int BUFFER_CAPACITY_DEFAULT = 20_000; public static final String BATCH_SIZE = "batch.size"; public static final int BATCH_SIZE_DEFAULT = 2_500; public static final String BATCH_FLUSH_MS = "batch.flush.ms"; public static final int BATCH_FLUSH_MS_DEFAULT = 15_000; public static final String COLLECTION_FORMAT = "mongo.collection.format"; public static final String RECORD_CONVERTER = "record.converter.class"; static final ConfigDef CONFIG_DEF = new ConfigDef() .define(MONGO_HOST, Type.STRING, NO_DEFAULT_VALUE, new NotEmptyString(), HIGH, "MongoDB host name to write data to", MONGO_GROUP, 0, ConfigDef.Width.MEDIUM, "MongoDB hostname") .define(MONGO_PORT, Type.INT, MONGO_PORT_DEFAULT, ConfigDef.Range.atLeast(1), LOW, "MongoDB port", MONGO_GROUP, 1, ConfigDef.Width.SHORT, "MongoDB port") .define(MONGO_DATABASE, Type.STRING, NO_DEFAULT_VALUE, new NotEmptyString(), HIGH, "MongoDB database name", MONGO_GROUP, 2, ConfigDef.Width.SHORT, "MongoDB database") .define(MONGO_USERNAME, Type.STRING, null, MEDIUM, "Username to connect to MongoDB database. If not set, no credentials are used.", MONGO_GROUP, 3, ConfigDef.Width.SHORT, "MongoDB username", Collections.singletonList(MONGO_PASSWORD)) .define(MONGO_PASSWORD, Type.STRING, null, MEDIUM, "Password to connect to MongoDB database. If not set, no credentials are used.", MONGO_GROUP, 4, ConfigDef.Width.SHORT, "MongoDB password", Collections.singletonList(MONGO_USERNAME)) .define(COLLECTION_FORMAT, Type.STRING, "{$topic}", new NotEmptyString(), MEDIUM, "A format string for the destination collection name, which may contain " + "`${topic}` as a placeholder for the originating topic name.\n" + "For example, `kafka_${topic}` for the topic `orders` will map to the " + "collection name `kafka_orders`.", MONGO_GROUP, 5, ConfigDef.Width.LONG, "MongoDB collection name format") .define(TOPICS_CONFIG, Type.LIST, NO_DEFAULT_VALUE, HIGH, "List of topics to be streamed.") .define(BUFFER_CAPACITY, Type.INT, BUFFER_CAPACITY_DEFAULT, ConfigDef.Range.atLeast(1), LOW, "Maximum number of items in a MongoDB writer buffer. Once the buffer becomes " + "full, the task fails.") .define(RECORD_CONVERTER, Type.CLASS, RecordConverterFactory.class, ValidClass.isSubclassOf(RecordConverterFactory.class), MEDIUM, "RecordConverterFactory that returns classes to convert Kafka SinkRecords to " + "BSON documents.") .define(BATCH_SIZE, Type.INT, BATCH_SIZE_DEFAULT, ConfigDef.Range.atLeast(1), LOW, "Batch size to initiate a MongoDB write operation. If the buffer" + " does not reach this capacity within batch.flush.ms, it will be" + " written anyway.") .define(BATCH_FLUSH_MS, Type.INT, BATCH_FLUSH_MS_DEFAULT, ConfigDef.Range.atLeast(0), LOW, "Flush a batch after this amount of milliseconds."); private Map<String, String> connectorConfig; @Override public String version() { return AppInfoParser.getVersion(); } @Override public void start(Map<String, String> props) { List<String> errorMessages = new ArrayList<>(); for (ConfigValue v : config().validate(props)) { if (!v.errorMessages().isEmpty()) { errorMessages.add("Property " + v.name() + " with value " + v.value() + " does not validate: " + String.join("; ", v.errorMessages())); } } if (!errorMessages.isEmpty()) { throw new ConfigException("Configuration does not validate: \n\t" + String.join("\n\t", errorMessages)); } connectorConfig = new HashMap<>(props); logger.info(Utility.convertConfigToString(connectorConfig)); } @Override public Class<? extends Task> taskClass() { return MongoDbSinkTask.class; } @Override public List<Map<String, String>> taskConfigs(int maxTasks) { logger.info("At most {} will be started", maxTasks); return Collections.nCopies(maxTasks, connectorConfig); } @Override public void stop() { logger.debug("Stop"); // Nothing to do since it has no background monitoring. } @Override public ConfigDef config() { return CONFIG_DEF; } }