├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs ├── org.eclipse.jdt.core.prefs └── org.eclipse.m2e.core.prefs ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── m2mci │ │ └── mqttKafkaBridge │ │ ├── Bridge.java │ │ └── CommandLineParser.java └── resources │ ├── .gitkeep │ └── log4j.properties └── test ├── java └── .gitkeep └── resources └── .gitkeep /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | mqttKafkaBridge 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/main/resources=UTF-8 4 | encoding//src/test/java=UTF-8 5 | encoding//src/test/resources=UTF-8 6 | encoding/=UTF-8 7 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 3 | org.eclipse.jdt.core.compiler.compliance=1.7 4 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 5 | org.eclipse.jdt.core.compiler.source=1.7 6 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=false 4 | version=1 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mqttKafkaBridge 2 | 3 | Bridge which consumes MQTT messages and republishes them on Kafka on the same topic. 4 | 5 | ## Usage 6 | 7 | $ java -jar mqttKafkaBridge.jar [options...] 8 | 9 | Where `options` are: 10 | 11 | --help (-h) : Show help 12 | --id VAL : MQTT Client ID 13 | --topics VAL : MQTT topic filters (comma-separated) 14 | --uri VAL : MQTT Server URI 15 | --zk VAL : Zookeeper connect string 16 | 17 | If you don't specify any command-line options, it uses the following defaults: 18 | 19 | id: mqttKafkaBridge 20 | topics: '#' (all topics) 21 | uri: tcp://localhost:1883 22 | zk: localhost:2181 23 | 24 | ***Note***: you can't run more than one bridge using the default settings, since two clients cannot connect to the same MQTT server with the same client ID. Additionally, you will get multiple messages published to Kafka for each message published to MQTT. If you wish to run multiple instances, you'll need to divide up the topics among the instances, and make sure to give them different IDs. 25 | 26 | ## Logging 27 | `mqttKafkaBridge` uses [log4j](http://logging.apache.org/log4j/2.x/) for logging, as do the [Paho](http://www.eclipse.org/paho/) and [Kafka](http://kafka.apache.org/) libraries it uses. There is a default `log4j.properties` file packaged with the jar which simply prints all messages of level `INFO` or greater to the console. If you want to customize logging, simply create your own `log4j.properties` file, and start up `mqttKafkaBridge` as follows: 28 | 29 | $ java -Dlog4j.configuration=file:///path/to/log4j.properties -jar mqttKafkaBridge.jar [options...] 30 | 31 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.m2mci 6 | mqttKafkaBridge 7 | 0.1.0 8 | 9 | 11 | 12 | 13 | scm:git:https://bitbucket.org/m2mconnections/mqttKafkaBridge.git 14 | 15 | 16 | 17 | 18 | 19 | 20 | maven-central 21 | Maven Central Repo 22 | https://repo.maven.apache.org/maven2 23 | 24 | 25 | 26 | eclipse-paho 27 | Eclipse Paho Repo 28 | https://repo.eclipse.org/content/repositories/paho-releases/ 29 | 30 | 31 | 32 | apache-kafka 33 | Apache Kafka Repo 34 | http://clojars.org/repo/ 35 | 36 | 37 | mvnrepository 38 | mvnrepository 39 | http://www.mvnrepository.com 40 | 41 | 42 | 43 | 44 | 45 | 46 | junit 47 | junit 48 | 4.11 49 | 50 | 51 | org.mockito 52 | mockito-core 53 | 1.9.5 54 | 55 | 56 | org.eclipse.paho 57 | mqtt-client 58 | 0.4.0 59 | 60 | 61 | org.apache.kafka 62 | kafka 63 | 0.7.2 64 | 65 | 66 | org.scala-lang 67 | scala-library 68 | 2.8.2 69 | 70 | 71 | args4j 72 | args4j 73 | 2.0.25 74 | 75 | 76 | com.101tec 77 | zkclient 78 | 0.3 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-compiler-plugin 89 | 3.1 90 | 91 | 1.7 92 | 1.7 93 | 94 | 95 | 96 | 97 | org.apache.maven.plugins 98 | maven-release-plugin 99 | 2.4.1 100 | 101 | 102 | org.apache.maven.plugins 103 | maven-javadoc-plugin 104 | 2.9 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-deploy-plugin 109 | 2.7 110 | 111 | 112 | org.apache.maven.plugins 113 | maven-source-plugin 114 | 2.2.1 115 | 116 | 117 | 118 | org.jacoco 119 | jacoco-maven-plugin 120 | 0.6.2.201302030002 121 | 122 | 123 | maven-assembly-plugin 124 | 125 | 126 | 127 | com.m2mci.mqttKafkaBridge.Bridge 128 | 129 | 130 | 131 | jar-with-dependencies 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | m2m-snapshot 142 | m2m-artifactory1-snapshots 143 | http://24.14.16.189:8081/artifactory/libs-snapshot-local 144 | 145 | 146 | m2m-release 147 | m2m-artifactory1-releases 148 | http://24.14.16.189:8081/artifactory/libs-release-local 149 | 150 | 151 | 152 | 153 | 154 | 156 | UTF-8 157 | UTF-8 158 | 159 | 160 | -------------------------------------------------------------------------------- /src/main/java/com/m2mci/mqttKafkaBridge/Bridge.java: -------------------------------------------------------------------------------- 1 | package com.m2mci.mqttKafkaBridge; 2 | 3 | import java.util.Properties; 4 | 5 | import kafka.javaapi.producer.Producer; 6 | import kafka.javaapi.producer.ProducerData; 7 | import kafka.message.Message; 8 | import kafka.producer.ProducerConfig; 9 | 10 | import org.apache.log4j.Logger; 11 | import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; 12 | import org.eclipse.paho.client.mqttv3.IMqttToken; 13 | import org.eclipse.paho.client.mqttv3.MqttAsyncClient; 14 | import org.eclipse.paho.client.mqttv3.MqttCallback; 15 | import org.eclipse.paho.client.mqttv3.MqttException; 16 | import org.eclipse.paho.client.mqttv3.MqttMessage; 17 | import org.kohsuke.args4j.CmdLineException; 18 | 19 | public class Bridge implements MqttCallback { 20 | private Logger logger = Logger.getLogger(this.getClass().getName()); 21 | private MqttAsyncClient mqtt; 22 | private Producer kafkaProducer; 23 | 24 | private void connect(String serverURI, String clientId, String zkConnect) throws MqttException { 25 | mqtt = new MqttAsyncClient(serverURI, clientId); 26 | mqtt.setCallback(this); 27 | IMqttToken token = mqtt.connect(); 28 | Properties props = new Properties(); 29 | props.put("zk.connect", zkConnect); 30 | props.put("serializer.class", "kafka.serializer.DefaultEncoder"); 31 | ProducerConfig config = new ProducerConfig(props); 32 | kafkaProducer = new Producer(config); 33 | token.waitForCompletion(); 34 | logger.info("Connected to MQTT and Kafka"); 35 | } 36 | 37 | private void reconnect() throws MqttException { 38 | IMqttToken token = mqtt.connect(); 39 | token.waitForCompletion(); 40 | } 41 | 42 | private void subscribe(String[] mqttTopicFilters) throws MqttException { 43 | int[] qos = new int[mqttTopicFilters.length]; 44 | for (int i = 0; i < qos.length; ++i) { 45 | qos[i] = 0; 46 | } 47 | mqtt.subscribe(mqttTopicFilters, qos); 48 | } 49 | 50 | @Override 51 | public void connectionLost(Throwable cause) { 52 | logger.warn("Lost connection to MQTT server", cause); 53 | while (true) { 54 | try { 55 | logger.info("Attempting to reconnect to MQTT server"); 56 | reconnect(); 57 | logger.info("Reconnected to MQTT server, resuming"); 58 | return; 59 | } catch (MqttException e) { 60 | logger.warn("Reconnect failed, retrying in 10 seconds", e); 61 | } 62 | try { 63 | Thread.sleep(10000); 64 | } catch (InterruptedException e) { 65 | } 66 | } 67 | } 68 | 69 | @Override 70 | public void deliveryComplete(IMqttDeliveryToken token) { 71 | // TODO Auto-generated method stub 72 | 73 | } 74 | 75 | @Override 76 | public void messageArrived(String topic, MqttMessage message) throws Exception { 77 | byte[] payload = message.getPayload(); 78 | ProducerData data = new ProducerData(topic, new Message(payload)); 79 | kafkaProducer.send(data); 80 | } 81 | 82 | /** 83 | * @param args 84 | */ 85 | public static void main(String[] args) { 86 | CommandLineParser parser = null; 87 | try { 88 | parser = new CommandLineParser(); 89 | parser.parse(args); 90 | Bridge bridge = new Bridge(); 91 | bridge.connect(parser.getServerURI(), parser.getClientId(), parser.getZkConnect()); 92 | bridge.subscribe(parser.getMqttTopicFilters()); 93 | } catch (MqttException e) { 94 | e.printStackTrace(System.err); 95 | } catch (CmdLineException e) { 96 | System.err.println(e.getMessage()); 97 | parser.printUsage(System.err); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/m2mci/mqttKafkaBridge/CommandLineParser.java: -------------------------------------------------------------------------------- 1 | package com.m2mci.mqttKafkaBridge; 2 | 3 | import java.io.OutputStream; 4 | import java.io.PrintStream; 5 | 6 | import org.kohsuke.args4j.CmdLineException; 7 | import org.kohsuke.args4j.CmdLineParser; 8 | import org.kohsuke.args4j.Option; 9 | 10 | public class CommandLineParser { 11 | private static final String ALL_MQTT_TOPICS = "#"; 12 | private static final String DEFAULT_ZOOKEEPER_CONNECT = "localhost:2181"; 13 | private static final String DEFAULT_MQTT_SERVER_URI = "tcp://localhost:1883"; 14 | 15 | @Option(name="--id", usage="MQTT Client ID") 16 | private String clientId = "mqttKafkaBridge"; 17 | 18 | @Option(name="--uri", usage="MQTT Server URI") 19 | private String serverURI = DEFAULT_MQTT_SERVER_URI; 20 | 21 | @Option(name="--zk", usage="Zookeeper connect string") 22 | private String zkConnect = DEFAULT_ZOOKEEPER_CONNECT; 23 | 24 | @Option(name="--topics", usage="MQTT topic filters (comma-separated)") 25 | private String mqttTopicFilters = ALL_MQTT_TOPICS; 26 | 27 | @Option(name="--help", aliases="-h", usage="Show help") 28 | private boolean showHelp = false; 29 | 30 | private CmdLineParser parser = new CmdLineParser(this); 31 | 32 | public String getClientId() { 33 | return clientId; 34 | } 35 | 36 | public String getServerURI() { 37 | return serverURI; 38 | } 39 | 40 | public String getZkConnect() { 41 | return zkConnect; 42 | } 43 | 44 | public String[] getMqttTopicFilters() { 45 | return mqttTopicFilters.split(","); 46 | } 47 | 48 | public void parse(String[] args) throws CmdLineException { 49 | parser.parseArgument(args); 50 | if (showHelp) { 51 | printUsage(System.out); 52 | System.exit(0); 53 | } 54 | } 55 | 56 | public void printUsage(OutputStream out) { 57 | PrintStream stream = new PrintStream(out); 58 | stream.println("java " + Bridge.class.getName() + " [options...]"); 59 | parser.printUsage(out); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/resources/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacklund/mqttKafkaBridge/f11a33759c1bba27f95d2e6ec417d9a8867c6094/src/main/resources/.gitkeep -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Set root logger level to INFO and its only appender to A1. 2 | log4j.rootLogger=INFO, A1 3 | 4 | # A1 is set to be a ConsoleAppender. 5 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 6 | 7 | # A1 uses PatternLayout. 8 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n -------------------------------------------------------------------------------- /src/test/java/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacklund/mqttKafkaBridge/f11a33759c1bba27f95d2e6ec417d9a8867c6094/src/test/java/.gitkeep -------------------------------------------------------------------------------- /src/test/resources/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacklund/mqttKafkaBridge/f11a33759c1bba27f95d2e6ec417d9a8867c6094/src/test/resources/.gitkeep --------------------------------------------------------------------------------