ActiveMQ onMessage never works with createDurableSubscriber - kotlin

I have been trying to find out why onMessage is not getting invoked in the below code. I am able to publish a set of objects to a Topic name "SampleTopic" successfully, but I am not able to consume from it. Nothing has worked for me. Please suggest.
val logger = KotlinLogging.logger {}
logger.info { "This is info log : Inside subscriber" }
val connFactory = ActiveMQConnectionFactory()
val conn = connFactory.createConnection()!!
conn.setClientID("SampleClient")
val sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE)
val dest = sess.createTopic("SampleTopic")
val cons : TopicSubscriber = sess.createDurableSubscriber(dest, "SampleSubscription")
logger.info { "This is info log : Inside subscriber $cons" }
conn.start()
Thread.sleep(10000)
logger.info { "This is info log : Inside subscriber $cons" }
cons.setMessageListener {
MessageListener() {
fun onMessage(message: Message?) {
try {
logger.info { "This is info log : Inside subscriber1 : $message" }
} catch (e: NumberFormatException) {
e.printStackTrace()
} catch (e: JMSException) {
e.printStackTrace()
}
}
}
}
logger.info { "This is info log : Inside subscriber90 $cons" }
conn.close()
enter image description here

As soon as you set the MessageListener you invoke close() on the connection. Closing the connection will close all related sessions along with all the producers and consumers created from those sessions. Therefore you won't be able to receive any messages. Once you set the MessageListener you'll need to keep the connection open so it can receive messages.
Keep in mind that the purpose of the MessageListener is to receive messages asynchronously. You need to keep the connection open during this time.
The simplest way to keep the connection open is just to insert a Thread.sleep() after you set the MessageListener. However, this effectively makes your client synchronous.
Alternatively, you can receive messages synchronously. This bit of code will consume all the messages it can until it doesn't receive a message for 10 seconds:
val logger = KotlinLogging.logger {}
logger.info { "This is info log : Inside subscriber" }
val connFactory = ActiveMQConnectionFactory()
val conn = connFactory.createConnection()!!
conn.setClientID("SampleClient")
val sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE)
val dest = sess.createTopic("SampleTopic")
val cons : TopicSubscriber = sess.createDurableSubscriber(dest, "SampleSubscription")
logger.info { "This is info log : Inside subscriber $cons" }
conn.start()
Thread.sleep(10000)
logger.info { "This is info log : Inside subscriber $cons" }
do {
val message = cons.receive(10000)
logger.info { "This is info log : $message" }
} while (message != null)
logger.info { "This is info log : Inside subscriber90 $cons" }
conn.close()

Related

Wait for channel close

I have a channel that I only use to convey that a connection was closed. I figured a Unit channel which I then close on disconnect would be the most appropriate. Simplified example:
class Connection {
val disconnect = Channel<Unit>(1, BufferOverflow.DROP_LATEST)
fun connect() {
backingConnection.disconnectCallback = {
disconnect.close()
}
backingConnection.connect()
}
}
Now the question is how to wait for this channel to close. Here are two options, both look a bit verbose:
lifecycleScope.launch {
try {
connection.disconnect.receive()
} catch (ignore: Exception) {}
finish()
}
or
lifecycleScope.launch {
#Suppress("ControlFlowWithEmptyBody")
for (dummy in connection.disconnect) {}
finish()
}
Are there better options?
I just found there is a receiveCatching() method I can use for this purpose:
lifecycleScope.launch {
connection.disconnect.receiveCatching()
finish()
}

Outputstream still saves old data

I have an application in which I can receive bluetooth messages, but whenever I write a message it will still remember data from the previous message. How do I clear this? Underneath is my code for sending the message:
private fun sendMessage(message: String) {
messages.add(message)
val time = SimpleDateFormat("HH:mm:ss").format(Date())
messageAdapter?.add(
"$message\n\t${bluetoothAdapter?.name} $time"
)
messageAdapter?.notifyDataSetChanged()
launchAsync {
asyncAwait {
try {
outputStream?.write(message.toByteArray(Charset.forName("UTF-8")))
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}

Using emit to build a Kotlin flow runs indefinitely and doesnt complete

I use a java library with which I can subscribe to events from my eventstore db.
I can create a subscription according to the following SubscirptionListener
public abstract class SubscriptionListener {
public void onEvent(Subscription subscription, ResolvedEvent event) {
}
public void onError(Subscription subscription, Throwable throwable) {
}
public void onCancelled(Subscription subscription) {
}
}
I would like to emit ResolvedEvents as part of a flow each time the subscription is triggered. However, the call to emit doesn't finish.
fun flowSubscriptionListener(
streamName: String,
options: SubscribeToStreamOptions = SubscribeToStreamOptions.get(),
onError: (subscription: Subscription?, throwable: Throwable) -> Unit = { _, _ -> },
onCancelled: (subscription: Subscription) -> Unit = { _ -> }
): Flow<ResolvedEvent> {
return flow {
val listener = object : SubscriptionListener() {
override fun onEvent(subscription: Subscription, event: ResolvedEvent) {
logger.info {
"Received event ${event.originalEvent.streamRevision}#${event.originalEvent.streamId}"
}
runBlocking {
logger.info { "emitting event" }
this#flow.emit(event)
logger.info { "Event emitted" }
}
}
override fun onError(subscription: Subscription?, throwable: Throwable) {
logger.error {
"Received error with message: ${throwable.message ?: "No message"} on subscription ${subscription?.subscriptionId}"
}
onError(subscription, throwable)
}
override fun onCancelled(subscription: Subscription) {
logger.debug { "Subscription ${subscription.subscriptionId} cancelled" }
onCancelled(subscription)
}
}
client.subscribeToStream(streamName, listener).await()
}.buffer(10)
}
I have a sample setup where I await a flow with three events
flowSubscriptionListener(
streamName = "SampleTournament-adb517b8-62e9-4305-b3b6-c1e7193a6d19",
).map {
it.event.eventType
}.collect {
println(it)
}
However, I receive no events at all. The console output shows me that invocation of emit never terminates.
[grpc-default-executor-1] INFO lib.eventstoredb.wrapper.EskWrapperEsdb - Received event 0#SampleTournament-adb517b8-62e9-4305-b3b6-c1e7193a6d19
[grpc-default-executor-1] INFO lib.eventstoredb.wrapper.EskWrapperEsdb - emitting event
I am expecting the logging of "Event emitted"
In order to wrap callback-based API, you should use callbackFlow instead. It supports concurrent emissions, which I think is likely your problem here.
Also, it will properly handle the cancellation of the subscription when the flow itself is cancelled (via awaitClose()).
Here is one way to do it:
fun EventStoreDBClient.flowSubscription(
streamName: String,
options: SubscribeToStreamOptions = SubscribeToStreamOptions.get(),
): Flow<ResolvedEvent> = callbackFlow {
val listener = object : SubscriptionListener() {
override fun onEvent(subscription: Subscription, event: ResolvedEvent) {
logger.info { "Received event ${event.originalEvent.streamRevision}#${event.originalEvent.streamId}" }
logger.info { "Emitting event" }
trySendBlocking(event)
logger.info { "Event emitted" }
}
override fun onError(subscription: Subscription?, throwable: Throwable) {
logger.error {
"Received error with message: ${throwable.message ?: "No message"} on subscription ${subscription?.subscriptionId}"
}
close(throwable)
}
override fun onCancelled(subscription: Subscription) {
logger.debug { "Subscription ${subscription.subscriptionId} cancelled" }
close()
}
}
val subscription = subscribeToStream(streamName, listener, options).await()
awaitClose {
subscription.stop()
}
}.buffer(10)
Note that I also converted it to an extension function on EventStoreDBClient, which seems appropriate here. And I removed the error/cancellation callbacks because Flow already handles those (you can put them back if you need them)

How to create a polling mechanism with kotlin coroutines?

I am trying to create a polling mechanism with kotlin coroutines using sharedFlow and want to stop when there are no subscribers and active when there is at least one subscriber. My question is, is sharedFlow the right choice in this scenario or should I use channel. I tried using channelFlow but I am unaware how to close the channel (not cancel the job) outside the block body. Can someone help? Here's the snippet.
fun poll(id: String) = channelFlow {
while (!isClosedForSend) {
try {
send(repository.getDetails(id))
delay(MIN_REFRESH_TIME_MS)
} catch (throwable: Throwable) {
Timber.e("error -> ${throwable.message}")
}
invokeOnClose { Timber.e("channel flow closed.") }
}
}
You can use SharedFlow which emits values in a broadcast fashion (won't emit new value until the previous one is consumed by all the collectors).
val sharedFlow = MutableSharedFlow<String>()
val scope = CoroutineScope(Job() + Dispatchers.IO)
var producer: Job()
scope.launch {
val producer = launch() {
sharedFlow.emit(...)
}
sharedFlow.subscriptionCount
.map {count -> count > 0}
.distinctUntilChanged()
.collect { isActive -> if (isActive) stopProducing() else startProducing()
}
fun CoroutineScope.startProducing() {
producer = launch() {
sharedFlow.emit(...)
}
}
fun stopProducing() {
producer.cancel()
}
First of all, when you call channelFlow(block), there is no need to close the channel manually. The channel will be closed automatically after the execution of block is done.
I think the "produce" coroutine builder function may be what you need. But unfortunately, it's still an experimental api.
fun poll(id: String) = someScope.produce {
invokeOnClose { Timber.e("channel flow closed.") }
while (true) {
try {
send(repository.getDetails(id))
// delay(MIN_REFRESH_TIME_MS) //no need
} catch (throwable: Throwable) {
Timber.e("error -> ${throwable.message}")
}
}
}
fun main() = runBlocking {
val channel = poll("hello")
channel.receive()
channel.cancel()
}
The produce function will suspended when you don't call the returned channel's receive() method, so there is no need to delay.
UPDATE: Use broadcast for sharing values across multiple ReceiveChannel.
fun poll(id: String) = someScope.broadcast {
invokeOnClose { Timber.e("channel flow closed.") }
while (true) {
try {
send(repository.getDetails(id))
// delay(MIN_REFRESH_TIME_MS) //no need
} catch (throwable: Throwable) {
Timber.e("error -> ${throwable.message}")
}
}
}
fun main() = runBlocking {
val broadcast = poll("hello")
val channel1 = broadcast.openSubscription()
val channel2 = broadcast.openSubscription()
channel1.receive()
channel2.receive()
broadcast.cancel()
}

Need help completing the kotlinlang suggested exercise supporting Webhooks with javafx

In the tutorial, they teach how to support real-time p2p command-line messaging using websockets by implementing both client and server. I'm trying to finish an exercise where I have the client input messages via a javafx gui and receive messages inside the gui in the form of a chat log (basically a chat room)
I'm having trouble simply starting up the gui and the websocket together. I tried GlobalScope.launch in hopes that both would get run, but only the GUI gets launched. If I use runBlocking instead, only the websocket is active.
Here's what I have so far.
Other issues:
Don't know how to reference the javafx label variable inside the outputMessages function, so that we can update the chatlog. I try placing the label variable in the global scope, but it only results in a compile error, so I put it back inside SAKApplication.
How to update the label field to move to the next line (tried adding "/n" but it literally added "\n")
import java.util.Queue
import java.util.LinkedList
//var a = Label("s")
val messagesToSend: Queue<String> = LinkedList<String>()
class SAKApplication : Application() {
val l = Label("no text")
override fun start(primaryStage: Stage) {
val btn = Button()
btn.text = "Say 'Hello World'"
btn.onAction = EventHandler<ActionEvent> { println("Hello World!") }
val root = StackPane()
root.children.add(btn)
val textField = TextField()
// a = l
// action event
val event: EventHandler<ActionEvent> =
EventHandler {
l.text += "/n" + textField.getText()
messagesToSend.add(textField.getText())
}
// when enter is pressed
textField.setOnAction(event)
// add textfield
root.children.add(textField)
root.children.add(l)
val scene = Scene(root, 300.0, 250.0)
if (primaryStage != null) {
primaryStage.title = "Hello World!"
primaryStage.scene = scene
primaryStage.show()
}
val client = HttpClient {
install(WebSockets)
}
GlobalScope.launch {
client.webSocket(method = HttpMethod.Get, host = "127.0.0.1", port = 8080, path = "/chat") {
while(true) {
val messageOutputRoutine = launch { outputMessages() }
val userInputRoutine = launch { inputMessages() }
userInputRoutine.join() // Wait for completion; either "exit" or error
messageOutputRoutine.cancelAndJoin()
}
}
}
client.close()
}
}
fun main(args: Array<String>) {
Application.launch(SAKApplication::class.java, *args)
}
suspend fun DefaultClientWebSocketSession.outputMessages() {
try {
for (message in incoming) {
message as? Frame.Text ?: continue
// a.text += "/n" + message.readText()
println(message.readText())
}
} catch (e: Exception) {
println("Error while receiving: " + e.localizedMessage)
}
}
suspend fun DefaultClientWebSocketSession.inputMessages() {
val name = readLine() ?: ""
send(name)
while (true) {
sleep(1)
if (messagesToSend.isEmpty()) { continue }
val message = messagesToSend.remove()
if (message.equals("exit", true)) return
try {
send(message)
} catch (e: Exception) {
println("Error while sending: " + e.localizedMessage)
return
}
}
}