2525import static com .mongodb .kafka .connect .util .Validators .emptyString ;
2626import static com .mongodb .kafka .connect .util .Validators .errorCheckingValueValidator ;
2727import static java .lang .String .format ;
28+ import static java .util .Arrays .asList ;
2829import static java .util .Collections .emptyList ;
2930import static java .util .Collections .singletonList ;
3031import static org .apache .kafka .common .config .ConfigDef .Width ;
5152
5253import com .mongodb .kafka .connect .source .json .formatter .JsonWriterSettingsProvider ;
5354import com .mongodb .kafka .connect .source .schema .AvroSchema ;
55+ import com .mongodb .kafka .connect .source .topic .mapping .TopicMapper ;
5456import com .mongodb .kafka .connect .util .ConfigHelper ;
5557import com .mongodb .kafka .connect .util .ConnectConfigException ;
5658import com .mongodb .kafka .connect .util .Validators ;
@@ -126,13 +128,43 @@ public class MongoSourceConfig extends AbstractConfig {
126128 private static final String OUTPUT_SCHEMA_INFER_VALUE_DISPLAY =
127129 "Enable Infer Schemas for the value" ;
128130
131+ public static final String TOPIC_MAPPER_CONFIG = "topic.mapper" ;
132+ private static final String TOPIC_MAPPER_DISPLAY = "The topic mapper class" ;
133+ private static final String TOPIC_MAPPER_DOC =
134+ "The class that determines the topic to write the source data to. "
135+ + "By default this will be based on the 'ns' field in the change stream document, "
136+ + "along with any configured prefix and suffix." ;
137+ private static final String TOPIC_MAPPER_DEFAULT =
138+ "com.mongodb.kafka.connect.source.topic.mapping.DefaultTopicMapper" ;
139+
129140 public static final String TOPIC_PREFIX_CONFIG = "topic.prefix" ;
130141 private static final String TOPIC_PREFIX_DOC =
131142 "Prefix to prepend to database & collection names to generate the name of the Kafka "
132- + "topic to publish data to." ;
143+ + "topic to publish data to. Used by the 'DefaultTopicMapper'. " ;
133144 private static final String TOPIC_PREFIX_DISPLAY = "Topic Prefix" ;
134145 private static final String TOPIC_PREFIX_DEFAULT = "" ;
135146
147+ public static final String TOPIC_SUFFIX_CONFIG = "topic.suffix" ;
148+ private static final String TOPIC_SUFFIX_DOC =
149+ "Suffix to append to database & collection names to generate the name of the Kafka "
150+ + "topic to publish data to. Used by the 'DefaultTopicMapper'." ;
151+ private static final String TOPIC_SUFFIX_DISPLAY = "Topic Suffix" ;
152+ private static final String TOPIC_SUFFIX_DEFAULT = "" ;
153+
154+ public static final String TOPIC_NAMESPACE_MAP_CONFIG = "topic.namespace.map" ;
155+ private static final String TOPIC_NAMESPACE_MAP_DISPLAY = "The namespace to topic map" ;
156+ private static final String TOPIC_NAMESPACE_MAP_DOC =
157+ "A json map that maps change stream document namespaces to topics.\n "
158+ + "For example: `{\" db\" : \" dbTopic\" , \" db.coll\" : \" dbCollTopic\" }` will map all "
159+ + "change stream documents from the `db` database to `dbTopic.<collectionName>` apart from"
160+ + "any documents from the `db.coll` namespace which map to the `dbCollTopic` topic.\n "
161+ + "If you want to map all messages to a single topic use `*`: "
162+ + "For example: `{\" *\" : \" everyThingTopic\" , \" db.coll\" : \" exceptionToTheRuleTopic\" }` "
163+ + "will map all change stream documents to the `everyThingTopic` apart from the `db.coll` "
164+ + "messages."
165+ + "Note: Any prefix and suffix configuration will still apply." ;
166+ private static final String TOPIC_NAMESPACE_MAP_DEFAULT = "" ;
167+
136168 public static final String PIPELINE_CONFIG = "pipeline" ;
137169 private static final String PIPELINE_DISPLAY = "The pipeline to apply to the change stream" ;
138170 private static final String PIPELINE_DOC =
@@ -292,7 +324,7 @@ public class MongoSourceConfig extends AbstractConfig {
292324
293325 public static final ConfigDef CONFIG = createConfigDef ();
294326 private static final List <Consumer <MongoSourceConfig >> INITIALIZERS =
295- singletonList (MongoSourceConfig ::validateCollection );
327+ asList (MongoSourceConfig ::validateCollection , MongoSourceConfig :: getTopicMapper );
296328
297329 public enum OutputFormat {
298330 JSON ,
@@ -314,6 +346,7 @@ public String value() {
314346 }
315347
316348 private final ConnectionString connectionString ;
349+ private TopicMapper topicMapper ;
317350
318351 public MongoSourceConfig (final Map <?, ?> originals ) {
319352 this (originals , true );
@@ -371,6 +404,16 @@ private void validateCollection() {
371404 }
372405 }
373406
407+ public TopicMapper getTopicMapper () {
408+ if (topicMapper == null ) {
409+ topicMapper =
410+ configureInstance (
411+ createInstance (
412+ TOPIC_MAPPER_CONFIG , getString (TOPIC_MAPPER_CONFIG ), TopicMapper .class ));
413+ }
414+ return topicMapper ;
415+ }
416+
374417 public JsonWriterSettings getJsonWriterSettings () {
375418 return createInstance (
376419 OUTPUT_JSON_FORMATTER_CONFIG ,
@@ -388,6 +431,11 @@ public boolean tolerateErrors() {
388431 .equals (ErrorTolerance .ALL );
389432 }
390433
434+ private <T extends Configurable > T configureInstance (final T instance ) {
435+ instance .configure (this );
436+ return instance ;
437+ }
438+
391439 private static ConfigDef createConfigDef () {
392440 ConfigDef configDef =
393441 new ConfigDef () {
@@ -516,18 +564,6 @@ public Map<String, ConfigValue> validateAll(final Map<String, String> props) {
516564 Width .MEDIUM ,
517565 COLLATION_DISPLAY );
518566
519- configDef .define (
520- TOPIC_PREFIX_CONFIG ,
521- Type .STRING ,
522- TOPIC_PREFIX_DEFAULT ,
523- null ,
524- Importance .LOW ,
525- TOPIC_PREFIX_DOC ,
526- group ,
527- ++orderInGroup ,
528- Width .MEDIUM ,
529- TOPIC_PREFIX_DISPLAY );
530-
531567 configDef .define (
532568 POLL_MAX_BATCH_SIZE_CONFIG ,
533569 Type .INT ,
@@ -552,6 +588,54 @@ public Map<String, ConfigValue> validateAll(final Map<String, String> props) {
552588 Width .MEDIUM ,
553589 POLL_AWAIT_TIME_MS_DISPLAY );
554590
591+ group = "Topic mapping" ;
592+ orderInGroup = 0 ;
593+ configDef .define (
594+ TOPIC_MAPPER_CONFIG ,
595+ ConfigDef .Type .STRING ,
596+ TOPIC_MAPPER_DEFAULT ,
597+ Validators .matching (FULLY_QUALIFIED_CLASS_NAME ),
598+ ConfigDef .Importance .HIGH ,
599+ TOPIC_MAPPER_DOC ,
600+ group ,
601+ ++orderInGroup ,
602+ ConfigDef .Width .LONG ,
603+ TOPIC_MAPPER_DISPLAY );
604+
605+ configDef .define (
606+ TOPIC_PREFIX_CONFIG ,
607+ Type .STRING ,
608+ TOPIC_PREFIX_DEFAULT ,
609+ null ,
610+ Importance .LOW ,
611+ TOPIC_PREFIX_DOC ,
612+ group ,
613+ ++orderInGroup ,
614+ Width .MEDIUM ,
615+ TOPIC_PREFIX_DISPLAY );
616+ configDef .define (
617+ TOPIC_SUFFIX_CONFIG ,
618+ Type .STRING ,
619+ TOPIC_SUFFIX_DEFAULT ,
620+ null ,
621+ Importance .LOW ,
622+ TOPIC_SUFFIX_DOC ,
623+ group ,
624+ ++orderInGroup ,
625+ Width .MEDIUM ,
626+ TOPIC_SUFFIX_DISPLAY );
627+ configDef .define (
628+ TOPIC_NAMESPACE_MAP_CONFIG ,
629+ Type .STRING ,
630+ TOPIC_NAMESPACE_MAP_DEFAULT ,
631+ errorCheckingValueValidator ("A valid JSON document" , ConfigHelper ::documentFromString ),
632+ Importance .HIGH ,
633+ TOPIC_NAMESPACE_MAP_DOC ,
634+ group ,
635+ ++orderInGroup ,
636+ Width .MEDIUM ,
637+ TOPIC_NAMESPACE_MAP_DISPLAY );
638+
555639 group = "Schema" ;
556640 orderInGroup = 0 ;
557641
0 commit comments