Solace Chat: Leveraging Solace for Peer-To-Peer Banter!

The pub/sub features of the Solace API provide a rich feature set which can be wrapped in a simple GUI to implement a peer-to-peer chat application. Solace Chat is a simple Java GUI wrapper around the Solace API.  It enables broadcast and private text message delivery to a peer-to-peer network of users connected to Solace Message Routers.  Users can leverage Solace’s Multi-Node Routing and WAN distribution capabilities to deliver messages from a local Solace Router to users connected to any other Router, provided there is an MNR link between the Routers on the network.

I originally developed this application in a few days to test connectivity during an internal Solace Hackathon where we configured the largest interconnected mesh of Solace Routers ever deployed running worldwide across every availability zone in Amazon EC2.  The idea was that each Solace engineer would connect the chat application to the hackathon VPN their own local VMR and if the neighbour links were configured correctly they would be able to broadcast a message to everyone on the VPN mesh to announce the fact they had finished.

Because the application was intended to be run locally while connecting to a global mesh of VPNs, I designed it so that there would be no central server.  Instead clients would broadcast their availability to all the other chat clients connected to the mesh and use Solace’s topic subscription features to subscribe to messages of interest from the other clients as they join.  The client sends out status updates to other members of the chat mesh so they can keep track other user’s status, such as if they are idle or have logged off.

Over the next few days I added a few more useful chat features such as private messaging and user status indication and the ability to ignore messages from individual users.  Here’s what it looks like:
solace-chat-app_pic-1

You can also see in the example above that the Blogger1 Handle in the directory is showing as orange to indicate they are idle.  A disconnected user is shown in grey like so:
solace-chat-app_pic-2

To initiate a private chat, you can right click on a user’s Handle:
solace-chat-app_pic-3

Then start typing with the named window selected:
solace-chat-app_pic-4

Unchecking the check box next to a user’s Handle will prevent that user’s broadcast (Public) messages from being displayed in your public chat window:
solace-chat-app_pic-5

By default, the chat client will subscribe to public messages from all the chat clients on the VPN mesh.  To prevent this you can uncheck the Auto Subscribe checkbox.  You will still see new users displayed in your directory, but you won’t get their public messages until you check their checkbox in the directory.

How Does It Work?

Clients announce themselves by sending a ping message containing their Handle to a directory topic: topic/SolaceChat/Directory/Ping/ which all clients subscribe to.  Any clients receiving Ping messages then update their directory and (if auto subscribe is on) subscribe to that user’s broadcast topic keyed to their Handle: topic/SolaceChat/Messages/Public/.  All messages received on Public topics are displayed in the Public chat messages window.  We also subscribe to our own Public messages so anything we send is displayed in our Public messages window too.

Private chat messages work in a similar way, each client subscribes to a topic for private messages: topic/SolaceChat/Messages/Private/ and can publish private messages to topic/SolaceChat/Messages/Private/.  The user we are private chatting with can respond on our private chat topic.

Here’s some code snippets showing how that works in the Solace Java API.

Creating Ping messages and adding initial Subscriptions

The code below also shows creating and subscribing to a Private messages topic, which is also based on our Handle.  This subscription will be unique per Handle so ensures these messages are not broadcast to everyone.

protected void connectClients() {
...	
	// Acquire a message consumer and open the data channel to the appliance.
	System.out.println("About to connect to appliance.");
	cons = session.getMessageConsumer(new SubMessageHandler());			
	// Create ping topic plus ping subscription.			
	topicPing = JCSMPFactory.onlyInstance().createTopic(chatTopicPrefix+pingTopicSuffix+Self);
	topicPingAll = JCSMPFactory.onlyInstance().createTopic(chatTopicPrefix+pingTopicSuffix+">");
	System.out.printf("Setting topic subscription '%s'...\n", topicPingAll.getName());
	// set the ping message to be the handle name
	pingBytes = txtFldHandle.getText().getBytes();
	session.addSubscription(topicPingAll);		
	// subscribe to our own messages
	topicSelf = JCSMPFactory.onlyInstance().createTopic(publicChatMessagesPrefix+Self);
	System.out.printf("Setting topic subscription '%s'...\n", topicSelf.getName());
	session.addSubscription(topicSelf);
	topicSelfPrivate =  JCSMPFactory.onlyInstance().createTopic(privateChatMessagesPrefix+Self);
	session.addSubscription(topicSelfPrivate);
	chatTopics.put("Public", topicSelf);

	subscribedUsersSet.add(txtFldHandle.getText());
	System.out.println("Connected!");

Receiving Chat Messages

In the code snippet below, received messages are checked for their type.  Ping messages and user status updates are processed and displayed in the user directory.  Chat messages are displayed in the Public or a Private chat message window, as appropriate.

public void onReceive(BytesXMLMessage msg) {
	String topicName = msg.getDestination().getName();

	if (topicName.contains(pingTopicSuffix)) {
		ByteBuffer bb = msg.getAttachmentByteBuffer();
		boolean disconnect = false;
		boolean idle = false;
		String content = new String(bb.array());
		String Handle = topicName.substring(topicPing.getName().lastIndexOf("/")+1);
		if (content.contentEquals("idle")) {
			idle = true;
		} else if (content.contentEquals("disconnect")) {
			disconnect = true;
		} 
		updateDirectoryListing(Handle, disconnect, idle);
	}
	if (topicName.contains(chatMessagesPrefix)) {
		System.out.println("AppID: "+msg.getApplicationMessageId());
		String Handle = msg.getApplicationMessageId().substring(msg.getApplicationMessageId().indexOf(":")+1);
		if (topicName.contains(publicChatMessagesPrefix)) {
			displayMessage(msg, Handle, true, false);
		} else {
			displayMessage(msg, Handle, false, false);
		}
...

Future Plans

The basic chat functionality is in place, but there are a number of areas we could leverage more of Solace’s advanced messaging features to improve things further:

  • Chat history/caching messages for offline users to catch up on the lolz. We could easily implement this with a SolCache instance running on our local message router.
  • Group chats – currently a private chat is between two users only.
  • Private messages are published on well known topics, so could easily be intercepted. Using ACLs and/or On Behalf Of subscriptions, private chat could be truly private.
  • Add in support for other protocols such as MQTT, so we can run it from a mobile phone.

Get Involved!

The full source code for the application is available on GitHub here: https://github.com/dwray/SolaceChat

Feel free to submit enhancement requests, or even better, add features yourself and submit a pull request.