2121import static org .apache .beam .vendor .guava .v32_1_2_jre .com .google .common .base .Preconditions .checkNotNull ;
2222
2323import com .google .auto .value .AutoValue ;
24+ import com .hivemq .client .mqtt .MqttGlobalPublishFilter ;
25+ import com .hivemq .client .mqtt .datatypes .MqttQos ;
26+ import com .hivemq .client .mqtt .mqtt3 .Mqtt3BlockingClient ;
27+ import com .hivemq .client .mqtt .mqtt3 .Mqtt3Client ;
28+ import com .hivemq .client .mqtt .mqtt3 .Mqtt3ClientBuilder ;
29+ import com .hivemq .client .mqtt .mqtt3 .message .publish .Mqtt3Publish ;
2430import java .io .IOException ;
2531import java .io .Serializable ;
32+ import java .net .URI ;
2633import java .nio .charset .StandardCharsets ;
2734import java .util .ArrayList ;
2835import java .util .Collections ;
2936import java .util .List ;
3037import java .util .NoSuchElementException ;
3138import java .util .Objects ;
39+ import java .util .Optional ;
3240import java .util .UUID ;
3341import java .util .concurrent .TimeUnit ;
34- import java .util .concurrent .TimeoutException ;
3542import org .apache .beam .sdk .coders .ByteArrayCoder ;
3643import org .apache .beam .sdk .coders .Coder ;
3744import org .apache .beam .sdk .coders .SerializableCoder ;
5259import org .apache .beam .sdk .values .PDone ;
5360import org .apache .beam .vendor .guava .v32_1_2_jre .com .google .common .annotations .VisibleForTesting ;
5461import org .checkerframework .checker .nullness .qual .Nullable ;
55- import org .fusesource .mqtt .client .BlockingConnection ;
56- import org .fusesource .mqtt .client .FutureConnection ;
57- import org .fusesource .mqtt .client .MQTT ;
58- import org .fusesource .mqtt .client .Message ;
59- import org .fusesource .mqtt .client .QoS ;
60- import org .fusesource .mqtt .client .Topic ;
6162import org .joda .time .Duration ;
6263import org .joda .time .Instant ;
6364import org .slf4j .Logger ;
@@ -299,29 +300,40 @@ private void populateDisplayData(DisplayData.Builder builder) {
299300 builder .addIfNotNull (DisplayData .item ("username" , getUsername ()));
300301 }
301302
302- private MQTT createClient () throws Exception {
303+ private Mqtt3BlockingClient createClient () throws Exception {
303304 LOG .debug ("Creating MQTT client to {}" , getServerUri ());
304- MQTT client = new MQTT ();
305- client .setHost (getServerUri ());
306- if (getUsername () != null ) {
307- LOG .debug ("MQTT client uses username {}" , getUsername ());
308- client .setUserName (getUsername ());
309- client .setPassword (getPassword ());
305+ URI uri = new URI (getServerUri ());
306+ String host = uri .getHost ();
307+ int port = uri .getPort ();
308+ if (port == -1 ) {
309+ port = "ssl" .equals (uri .getScheme ()) || "tls" .equals (uri .getScheme ()) ? 8883 : 1883 ;
310+ }
311+
312+ Mqtt3ClientBuilder builder = Mqtt3Client .builder ().serverHost (host ).serverPort (port );
313+
314+ if ("ssl" .equals (uri .getScheme ()) || "tls" .equals (uri .getScheme ())) {
315+ builder = builder .sslWithDefaultConfig ();
310316 }
311- if (getClientId () != null ) {
312- String clientId = getClientId () + "-" + UUID .randomUUID ().toString ();
313- clientId =
314- clientId .substring (0 , Math .min (clientId .length (), MQTT_3_1_MAX_CLIENT_ID_LENGTH ));
315- LOG .debug ("MQTT client id set to {}" , clientId );
316- client .setClientId (clientId );
317+
318+ String clientId = getClientId ();
319+ if (clientId == null ) {
320+ clientId = UUID .randomUUID ().toString ();
317321 } else {
318- String clientId = UUID .randomUUID ().toString ();
319- clientId =
320- clientId .substring (0 , Math .min (clientId .length (), MQTT_3_1_MAX_CLIENT_ID_LENGTH ));
321- LOG .debug ("MQTT client id set to random value {}" , clientId );
322- client .setClientId (clientId );
322+ clientId = clientId + "-" + UUID .randomUUID ().toString ();
323323 }
324- return client ;
324+ clientId = clientId .substring (0 , Math .min (clientId .length (), MQTT_3_1_MAX_CLIENT_ID_LENGTH ));
325+ LOG .debug ("MQTT client id set to {}" , clientId );
326+ builder = builder .identifier (clientId );
327+
328+ if (getUsername () != null ) {
329+ LOG .debug ("MQTT client uses username {}" , getUsername ());
330+ var auth = builder .simpleAuth ().username (getUsername ());
331+ if (getPassword () != null ) {
332+ auth = auth .password (getPassword ().getBytes (StandardCharsets .UTF_8 ));
333+ }
334+ builder = auth .applySimpleAuth ();
335+ }
336+ return builder .buildBlocking ();
325337 }
326338 }
327339
@@ -429,9 +441,9 @@ public void populateDisplayData(DisplayData.Builder builder) {
429441 static class MqttCheckpointMark implements UnboundedSource .CheckpointMark , Serializable {
430442
431443 @ VisibleForTesting String clientId ;
432- @ VisibleForTesting transient List <Message > messages = new ArrayList <>();
444+ @ VisibleForTesting transient List <Mqtt3Publish > messages = new ArrayList <>();
433445
434- public MqttCheckpointMark (String id , List <Message > messages ) {
446+ public MqttCheckpointMark (String id , List <Mqtt3Publish > messages ) {
435447 this .clientId = id ;
436448 this .messages = messages ;
437449 }
@@ -444,9 +456,9 @@ public MqttCheckpointMark(String id, List<Message> messages) {
444456 @ Override
445457 public void finalizeCheckpoint () {
446458 LOG .debug ("Finalizing checkpoint acknowledging pending messages for client ID {}" , clientId );
447- for (Message message : messages ) {
459+ for (Mqtt3Publish message : messages ) {
448460 try {
449- message .ack ();
461+ message .acknowledge ();
450462 } catch (Exception e ) {
451463 LOG .warn ("Can't ack message for client ID {}" , clientId , e );
452464 }
@@ -480,7 +492,7 @@ public int hashCode() {
480492 static class Preparer {
481493 @ VisibleForTesting String clientId ;
482494 @ VisibleForTesting Instant oldestMessageTimestamp = Instant .now ();
483- @ VisibleForTesting transient List <Message > messages = new ArrayList <>();
495+ @ VisibleForTesting transient List <Mqtt3Publish > messages = new ArrayList <>();
484496
485497 public Preparer (MqttCheckpointMark checkpointMark ) {
486498 clientId = checkpointMark .clientId ;
@@ -493,15 +505,15 @@ public Preparer(String id) {
493505
494506 public Preparer () {}
495507
496- public void add (Message message , Instant timestamp ) {
508+ public void add (Mqtt3Publish message , Instant timestamp ) {
497509 if (timestamp .isBefore (oldestMessageTimestamp )) {
498510 oldestMessageTimestamp = timestamp ;
499511 }
500512 messages .add (message );
501513 }
502514
503515 MqttCheckpointMark newCheckpoint () {
504- List <Message > currentMessages = messages ;
516+ List <Mqtt3Publish > currentMessages = messages ;
505517 messages = new ArrayList <>();
506518 oldestMessageTimestamp = Instant .now ();
507519 return new MqttCheckpointMark (clientId , currentMessages );
@@ -532,7 +544,8 @@ public UnboundedReader<T> createReader(
532544 new UnboundedMqttReader <>(
533545 this ,
534546 preparer ,
535- message -> (T ) MqttRecord .of (message .getTopic (), message .getPayload ()));
547+ message ->
548+ (T ) MqttRecord .of (message .getTopic ().toString (), message .getPayloadAsBytes ()));
536549 } else {
537550 unboundedMqttReader = new UnboundedMqttReader <>(this , preparer );
538551 }
@@ -570,12 +583,13 @@ static class UnboundedMqttReader<T> extends UnboundedSource.UnboundedReader<T> {
570583
571584 private final UnboundedMqttSource <T > source ;
572585
573- private MQTT client ;
574- private BlockingConnection connection ;
586+ private Mqtt3BlockingClient client ;
587+ private Mqtt3BlockingClient .Mqtt3Publishes publishes ;
588+ private String clientId = "" ;
575589 private T current ;
576590 private Instant currentTimestamp ;
577591 private final MqttCheckpointMark .Preparer checkpointPreparer ;
578- private SerializableFunction <Message , T > extractFn ;
592+ private SerializableFunction <Mqtt3Publish , T > extractFn ;
579593
580594 public UnboundedMqttReader (
581595 UnboundedMqttSource <T > source , MqttCheckpointMark .Preparer checkpointPreparer ) {
@@ -586,13 +600,13 @@ public UnboundedMqttReader(
586600 } else {
587601 this .checkpointPreparer = new MqttCheckpointMark .Preparer ();
588602 }
589- this .extractFn = message -> (T ) message .getPayload ();
603+ this .extractFn = message -> (T ) message .getPayloadAsBytes ();
590604 }
591605
592606 public UnboundedMqttReader (
593607 UnboundedMqttSource <T > source ,
594608 MqttCheckpointMark .Preparer checkpointPreparer ,
595- SerializableFunction <Message , T > extractFn ) {
609+ SerializableFunction <Mqtt3Publish , T > extractFn ) {
596610 this (source , checkpointPreparer );
597611 this .extractFn = extractFn ;
598612 }
@@ -603,11 +617,20 @@ public boolean start() throws IOException {
603617 Read <T > spec = source .spec ;
604618 try {
605619 client = spec .connectionConfiguration ().createClient ();
606- LOG .debug ("Reader client ID is {}" , client .getClientId ());
607- checkpointPreparer .clientId = client .getClientId ().toString ();
608- connection = createConnection (client );
609- connection .subscribe (
610- new Topic [] {new Topic (spec .connectionConfiguration ().getTopic (), QoS .AT_LEAST_ONCE )});
620+ this .clientId = client .getConfig ().getClientIdentifier ().map (Object ::toString ).orElse ("" );
621+ LOG .debug ("Reader client ID is {}" , clientId );
622+ checkpointPreparer .clientId = clientId ;
623+ client .connect ();
624+
625+ // Subscribe and get the publishes stream with manual acks enabled
626+ publishes = client .publishes (MqttGlobalPublishFilter .ALL , true );
627+
628+ client
629+ .subscribeWith ()
630+ .topicFilter (spec .connectionConfiguration ().getTopic ())
631+ .qos (MqttQos .AT_LEAST_ONCE )
632+ .send ();
633+
611634 return advance ();
612635 } catch (Exception e ) {
613636 throw new IOException (e );
@@ -617,11 +640,12 @@ public boolean start() throws IOException {
617640 @ Override
618641 public boolean advance () throws IOException {
619642 try {
620- LOG .trace ("MQTT reader (client ID {}) waiting message ..." , client . getClientId () );
621- Message message = connection .receive (1 , TimeUnit .SECONDS );
622- if (message == null ) {
643+ LOG .trace ("MQTT reader (client ID {}) waiting message ..." , clientId );
644+ Optional < Mqtt3Publish > messageOpt = publishes .receive (1 , TimeUnit .SECONDS );
645+ if (! messageOpt . isPresent () ) {
623646 return false ;
624647 }
648+ Mqtt3Publish message = messageOpt .get ();
625649 current = this .extractFn .apply (message );
626650 currentTimestamp = Instant .now ();
627651 checkpointPreparer .add (message , currentTimestamp );
@@ -633,13 +657,24 @@ public boolean advance() throws IOException {
633657
634658 @ Override
635659 public void close () throws IOException {
636- LOG .debug ("Closing MQTT reader (client ID {})" , client .getClientId ());
637- try {
638- if (connection != null ) {
639- connection .disconnect ();
660+ LOG .debug ("Closing MQTT reader (client ID {})" , clientId );
661+ if (publishes != null ) {
662+ try {
663+ publishes .close ();
664+ } catch (Exception e ) {
665+ LOG .warn ("Error closing publishes stream" , e );
666+ } finally {
667+ publishes = null ;
668+ }
669+ }
670+ if (client != null ) {
671+ try {
672+ client .disconnect ();
673+ } catch (Exception e ) {
674+ throw new IOException (e );
675+ } finally {
676+ client = null ;
640677 }
641- } catch (Exception e ) {
642- throw new IOException (e );
643678 }
644679 }
645680
@@ -764,8 +799,7 @@ private static class WriteFn<InputT> extends DoFn<InputT, Void> {
764799 private final SerializableFunction <InputT , byte []> payloadFn ;
765800 private final boolean retained ;
766801
767- private transient MQTT client ;
768- private transient BlockingConnection connection ;
802+ private transient Mqtt3BlockingClient client ;
769803
770804 public WriteFn (Write <InputT > spec ) {
771805 this .spec = spec ;
@@ -783,8 +817,9 @@ public WriteFn(Write<InputT> spec) {
783817 public void createMqttClient () throws Exception {
784818 LOG .debug ("Starting MQTT writer" );
785819 this .client = this .spec .connectionConfiguration ().createClient ();
786- LOG .debug ("MQTT writer client ID is {}" , client .getClientId ());
787- this .connection = createConnection (client );
820+ String clientId = client .getConfig ().getClientIdentifier ().map (Object ::toString ).orElse ("" );
821+ LOG .debug ("MQTT writer client ID is {}" , clientId );
822+ this .client .connect ();
788823 }
789824
790825 @ ProcessElement
@@ -793,32 +828,25 @@ public void processElement(ProcessContext context) throws Exception {
793828 byte [] payload = this .payloadFn .apply (element );
794829 String topic = this .topicFn .apply (element );
795830 LOG .debug ("Sending message {}" , new String (payload , StandardCharsets .UTF_8 ));
796- this .connection .publish (topic , payload , QoS .AT_LEAST_ONCE , this .retained );
831+
832+ client
833+ .publishWith ()
834+ .topic (topic )
835+ .payload (payload )
836+ .qos (MqttQos .AT_LEAST_ONCE )
837+ .retain (this .retained )
838+ .send ();
797839 }
798840
799841 @ Teardown
800842 public void closeMqttClient () throws Exception {
801- if (this .connection != null ) {
802- LOG .debug ("Disconnecting MQTT connection (client ID {})" , client .getClientId ());
803- this .connection .disconnect ();
843+ if (this .client != null ) {
844+ String clientId =
845+ client .getConfig ().getClientIdentifier ().map (Object ::toString ).orElse ("" );
846+ LOG .debug ("Disconnecting MQTT connection (client ID {})" , clientId );
847+ this .client .disconnect ();
804848 }
805849 }
806850 }
807851 }
808-
809- /** Create a connected MQTT BlockingConnection from given client, aware of connection timeout. */
810- static BlockingConnection createConnection (MQTT client ) throws Exception {
811- FutureConnection futureConnection = client .futureConnection ();
812- org .fusesource .mqtt .client .Future <Void > connecting = futureConnection .connect ();
813- while (true ) {
814- try {
815- connecting .await (1 , TimeUnit .MINUTES );
816- } catch (TimeoutException e ) {
817- LOG .warn ("Connection to {} pending after waiting for 1 minute" , client .getHost ());
818- continue ;
819- }
820- break ;
821- }
822- return new BlockingConnection (futureConnection );
823- }
824852}
0 commit comments