This blog post continues from my previous post where I introduced the On-Behalf-Of (OBO) subscription manager application architecture. In this post, I’ll show you the basic code required to get this application pattern working. To do this I will show you a basic client application and a subscription manager application which can subscribe on behalf of this client. The picture below gives you an overview of this pattern as a refresher. Jump back to my previous post if any of this is new to you.
Assumptions
To get the most out of this blog post you should be familiar with the following things:
- You are familiar with Solace core concepts.
- You are familiar with the mechanisms of publishing and consuming direct messages as detailed in the publish/subscribe tutorial.
- You have read my blog post explaining the purpose of an OBO Subscription Manager.
- You have access to a running Solace message router with the following configuration:
- Enabled message VPN
One simple way to get access to a Solace message router is to start a Solace VMR load as outlined here. By default the Solace VMR will run with the “default” message VPN configured and ready for messaging. Going forward, this blog post will assume that you are using the Solace VMR. If you are using a different Solace message router configuration, adapt the instructions to match your configuration.
Goals
The goal of this blog is to demonstrate a typical interaction between a client application and an OBO subscription manager using the Solace router. This tutorial will show you:
- How to control entitlements natively on the Solace router with Access Control Lists (ACLs).
- How to build and send a message on a “well known” topic to the OBO subscription manager.
- How one application can make subscriptions On-behalf-Of another application. .
Solace message router properties
In order to send or receive messages to a Solace message router, you need to know a few details of how to connect to the Solace message router. Specifically you need to know the following:
Resource | Value | Description |
---|---|---|
Host | String of the form < |
This is the address clients use when connecting to the Solace message router to send and receive messages. For a Solace VMR this there is only a single interface so the IP is the same as the management IP address. For Solace message router appliances this is the host address of the message-backbone. |
Message VPN | String | The Solace message router Message VPN that this client should connect to. The simplest option is to use the “default” message-vpn which is present on all Solace message routers and fully enabled for message traffic on Solace VMRs. |
For the purposes of this tutorial, you will connect to the default message VPN of a Solace VMR and supply the usernames and passwords you will create for the OBO client, the OBO Subscription Manager and the content publisher.
Obtaining the Solace API
This tutorial depends on you having the Java API downloaded and available. The Java API library can be downloaded here. The Java API is distributed as a zip file containing the required jars, API documentation, and examples. The instructions in this tutorial assume you have downloaded the Java API library and unpacked it to a known location. If your environment differs then adjust the build instructions appropriately.
The API comes with a set of samples. In the lib folder for these samples are a set of jar files which this tutorial depends on.
Creating the client usernames for this tutorial
Let’s first create the client usernames that we will need for this tutorial. To complete this tutorial, you will need to create three new client usernames: one for the OBO client, one for the OBO subscription manager and one for the sample content publisher. Using the Solace Command Line interface (CLI), issue the following commands (or just copy and paste from here!).
enable configure create client-username oboManager message-vpn default password default subscription-manager no shutdown end
This is the client username for the OBO subscription manager. We have created a new client username using all default values, with the notable exception of enabling the subscription manager setting. This setting is what allows the OBO subscriptions to be made.
Next, let’s create a second client username called “oboClient”. This time, we’ll create a new ACL profile and assign it to this client username. This ACL profile which we shall call “oboClientACL” should have its default subscribe action set to disallow, and have no exceptions. This is the client username which the client application will connect with. Its absence of subscribe permissions is part of the value proposition of the OBO subscription manager, in that the subscription manager can make subscriptions on behalf of the client, despite the client not having those permissions pre-defined on the router! Here’s the how all of that is done in the CLI:
enable configure create acl-profile oboClientACL message-vpn default subscribe-topic default-action disallow exit exit create client-username oboClient message-vpn default acl-profile oboClientACL password default no shutdown end
We have just configured the oboClient client username which is not entitled to subscribe to anything.
Next, let’s create a client username for our example content publisher. For simplicity, we won’t restrict it in any way.
enable configure create client-username contentPublisher message-vpn default password default no shutdown end
Finally, just to ensure we have no holes in our solution, we will disable the default client username. This is done with the following CLI commands:
enable configure client-username default message-vpn default shutdown All clients using this Client Username will be disconnected. Do you want to continue (y/n)? Y end
For a complete overview of all configuration options for client applications, follow the Solace documentation that describes how to configure client username accounts.
Connecting the client and sending the request
Just like the https://docs.solace.com/Solace-PubSub-Messaging-APIs/Java-API/java-api-home.htm”>publish/subscribe example, the client must first connect to the router. The host address or your router or VMR is passed in on the command line. The VPN name, client username and password are hard coded:
final JCSMPProperties properties = new JCSMPProperties(); properties.setProperty(JCSMPProperties.HOST, args[0]); // msg-backbone ip:port properties.setProperty(JCSMPProperties.VPN_NAME, "default"); properties.setProperty(JCSMPProperties.USERNAME, "oboClient"); properties.setProperty(JCSMPProperties.PASSWORD, "default"); final JCSMPSession session = JCSMPFactory.onlyInstance().createSession(properties); session.connect();
At this point your OBO client is connected to the Solace message router. You can use SolAdmin or the CLI to view the client connection and related details.
The next step is to create both a publisher object and a consumer object. The client needs the publisher object to send the request to the OBO subscription manager, and the consumer object is required to receive the reply. In the anonymous inner consumer class, we’ll dump any messages received out to the screen. This will allow us to prove that messages published from the sample content publisher are being consumed by the OBO Client after the OBO subscription has been successfully made.
XMLMessageProducer prod = session.getMessageProducer(new JCSMPStreamingPublishEventHandler() { public void responseReceived(String messageID) { System.out.println("Producer received response for msg: " + messageID); } public void handleError(String messageID, JCSMPException e, long timestamp) { System.out.printf("Producer received error for msg: %s@%s - %s%n", messageID, timestamp, e); } }); final XMLMessageConsumer cons = session.getMessageConsumer(new XMLMessageListener(){ public void onReceive(BytesXMLMessage msg) { System.out.println("-------------------"); System.out.println("OBO Client has received a content message:"); if (msg instanceof TextMessage) { TextMessage txtMsg = (TextMessage) msg; String text = txtMsg.getText(); System.out.println(text); } } public void onException(JCSMPException e) { System.out.printf("Consumer received exception: %s%n", e); } }); cons.start(); System.out.println("Consumer and producer created...");
After this setup, we need to create and send the request message to the OBO subscription manager, and receive the reply. This done using a Solace Requestor object, which implements this classic Message Exchange Pattern. There are a large set of possible message formats which your organization might choose to employ. You might send JSON, XML or some other structured content as a stream, blob, a simple text message or as a map. In this tutorial, we will use a Solace Map message to pass the two fields of data which the client sends to the OBO subscription manager: the client name and the topic being requested for subscription.
The reply will either contain “ok” or an error message if the subscription fails for any reason. Note that this reply is being returned from the OBO subscription manager.
After the reply is received, this program waits for user input before it quits, giving you the opportunity to check the Solace router and confirm that the subscription has been made. During this time, messages should be arriving from the publisher, and should be dumped to the screen.
final Topic topic = JCSMPFactory.onlyInstance().createTopic(oboAgentsWellKnownTopic); MapMessage requestMsg = JCSMPFactory.onlyInstance().createMessage(MapMessage.class); // note that the client name is NOT the client username - it is unique to each session String myClientName = (String) session.getProperty(JCSMPProperties.CLIENT_NAME); SDTMap map = JCSMPFactory.onlyInstance().createMap(); map.putString("clientName", myClientName); map.putString("requestedTopic", requestedTopic); requestMsg.setMap(map); System.out.printf("Connected. About to send request '%s' to topic '%s'...%n", requestedTopic, topic.getName()); // Solace request/reply... send the request to the agent and await its response Requestor requestor = session.createRequestor(); try { TextMessage responseMsg = (TextMessage) requestor.request(requestMsg, 5000, topic); if (responseMsg != null) { if (responseMsg instanceof TextMessage) { System.out.println("recieved a reply: " + responseMsg.dump()); System.out.println("Check the router to see your subscription to '" + requestedTopic + "'."); System.out.println("Press <return> to exit."); System.in.read(); } } } catch (JCSMPRequestTimeoutException ex) { System.out.println("No reply was recieved."); } // close up and quit session.closeSession();
Connecting the manager and processing the request
Similar to the client application, the OBO subscription manager must first connect to the router. Notice that after connecting, this program double checks to confirm that you enabled the subscription manager property on the “oboManager” client username. If you forgot to do this, the program will complain and quit.
final JCSMPProperties properties = new JCSMPProperties(); properties.setProperty(JCSMPProperties.HOST, args[0]); // msg-backbone ip:port properties.setProperty(JCSMPProperties.VPN_NAME, "default";); properties.setProperty(JCSMPProperties.USERNAME, "oboManager"); properties.setProperty(JCSMPProperties.PASSWORD, "default"); final JCSMPSession session = JCSMPFactory.onlyInstance().createSession(properties); session.connect(); if (!session.isCapable(CapabilityType.SUBSCRIPTION_MANAGER)) { System.out.println("This agent's client username 'oboManager' must have” + “ subscription manager' enabled."); System.exit(0); }
The next step, just like the client, is to create the publisher and the consumer. In this snippet of code below, the processing of the request is done directly in the onRecieve() method. The agent gets the client username and requested topic from the Map message. This is where a typical implementation would then interact with an external entitlement system to determine if the client should be allowed to subscribe to the topic. In this example, we will simply go ahead and assume the client is entitled, and make the subscription on its behalf.
Notice the flag passed into the addSubscription() method. The WAIT_FOR_CONFIRMflag tells the API to not return from this call until an acknowledgment has been received from the router. This is the safest option, allowing the agent to ensure it sends an accurate reply back to the client. There are scenarios where it may be appropriate to not wait. One such example is when the agent might be making a large number of subscriptions for the same client. In this case you could wait on only the last subscription to increase efficiency.
Finally, when it’s all done, we reply back to the client with a simple text based status of “ok”. Also note that the latchvariable is a CountDownLatch which this simple example uses to tell the main program thread that the client subscription has been made. This simple example program will just make one client subscription and then exit. In contrast, a typical production OBO agent would stay up and running indefinitely.
final XMLMessageProducer prod = session.getMessageProducer(new JCSMPStreamingPublishEventHandler() { public void responseReceived(String messageID) { System.out.println("Producer received response for msg: " + messageID); } public void handleError(String messageID, JCSMPException e, long timestamp) { System.out.printf("Producer received error for msg: %s@%s - %s%n", messageID, timestamp, e); } }); final XMLMessageConsumer cons = session.getMessageConsumer(new XMLMessageListener(){ public void onReceive(BytesXMLMessage msg) { if (msg instanceof MapMessage) { MapMessage mapMsg = (MapMessage) msg; try { SDTMap map = mapMsg.getMap(); String clientName = map.getString("clientName"); String topicRequested = map.getString("requestedTopic"); ClientName clientNameObject = JCSMPFactory.onlyInstance().createClientName(clientName); Topic requestedTopic = JCSMPFactory.onlyInstance().createTopic(topicRequested); String replyText = "ok"; try { session.addSubscription(clientNameObject, requestedTopic, JCSMPSession.WAIT_FOR_CONFIRM); } catch (JCSMPException e) { replyText = "ERROR: " + e.getMessage(); } TextMessage reply = JCSMPFactory.onlyInstance().createMessage(TextMessage.class); reply.setText(replyText); prod.sendReply(mapMsg, reply); latch.countDown(); } catch (JCSMPException e) { e.printStackTrace(); } } } public void onException(JCSMPException e) { System.out.printf("Consumer received exception: %s%n", e); } }); cons.start(); System.out.println("Consumer and producer created...");
Finally, with the producer and consumer objects defined including the request handler, we’ll go ahead and make the subscription on the router so that the OBO subscription manager is listening for subscription requests on the well-known topic which the client will use to send the request.
// subscribe to 'obo', the topic this agent will accept requests on. JCSMPFactory fact = JCSMPFactory.onlyInstance(); final Topic agentServiceTopic = fact.createTopic(oboAgentsWellKnownTopic); session.addSubscription(agentServiceTopic, true);
At this point, the client has been successfully subscribed and notified of that success. it’s ready to receive messages on the requested topic.
The example content producer
To complete this example, I have included a simple content producer which slowly pumps some simple text based content on the a/b/c topic which the OBO client ends up subscribed to in this example. In doing so, we prove that the OBO subscription has worked as expected.
This program is very similar to the publisher in the publish/subscribe tutorial, and hence we won’t examine all the code here.
Compiling and running this example
Combining the example source code shown above results in the following source code files:
- OBOSubscriptionManager.java
- OBOClient.java
- ContentProducer.java
Building
Building these examples is fairly simple. The following provides an example using Linux. These instructions assume you have unpacked the Solace Java API into a directory next to these files which you just downloaded. There are many suitable ways to build and execute this tutorial in Java. Adapt these instructions to suit your needs depending on your environment.
In the following examples replace VERSION with the Solace API version you downloaded.
javac -cp sol-jcsmp-VERSION/lib/*:. OBOSubscriptionManager.java javac -cp sol-jcsmp-VERSION/lib/*:. OBOClient.java javac -cp sol-jcsmp-VERSION/lib/*:. ContentProducer.java
Sample Output
Each of these 3 programs requires that you pass in the IP address of your VMR or router as a command line argument, as denoted by the “HOST” argument in the sample command line invocations below.
If you start the OBOAgent it will connect and wait for request messages.
$ java -cp sol-jcsmp-VERSION/lib/*:. OBOSubscriptionManager HOST OBOSubscriptionManager initializing... Consumer and producer created...
Then you can launch the client to send the request message. If successful, the output for the client will look like the following:
$ java -cp sol-jcsmp-VERSION/lib/*:. OBOClient HOST OBOClient initializing... Consumer and producer created... Connected. About to send request 'a/b/c' to topic 'obo'... received a reply: Destination: Topic '#P2P/v:lab-128-40/oboClient_1453387749814/#' CorrelationId: #REQ0 Priority: 0 Class Of Service: USER_COS_1 DeliveryMode: DIRECT Message Id: 3 Reply Message Binary Attachment: len=5 1c 05 6f 6b 00 ..ok. Check the router to see your subscription to 'a/b/c'. Press <return> to exit.
Note the body of the reply contains “ok”. This is the acknowledgment from the subscription manager. The OBOClient now sits and waits. Just hit the return key when you want to stop it, after you have proven the subscription works by running the content producer and seeing the messages received by the OBOClient.
With the subscription request now completed, the OBOSubscriptionManager output will look like the following:
TextMessage received: from client 'oboClient_1453388126220 for topic a/b/c This request will be allowed; making subscription on behalf of the client. The subscription has been successfully made on the router. Exiting.
Finally, run the content producer.
$ java -cp sol-jcsmp-VERSION/lib/*:. ContentProducer HOST ContentProducerinitializing... Content Publisher is connected and will now publish 60 messages on topic a/b/c .......................
After the producer is up and running, you should see this in your OBOclient’s output:
------------------- OBO Client has received a content message: Content message #1 ------------------- OBO Client has received a content message: Content message #2 -------------------
The client will continue dumping these content messages to the screen until you terminate it, or 60 seconds later the content publisher completes and exists.
Summary
You have now successfully connected an OBO client and subscription manager, made the client request a subscription, and have seen the OBO subscription manager make the subscription on behalf of the client.
In this series of blog articles we have examined the advantages realized when using an OBO subscription manager, and examined how to code the exchange between OBO clients and an OBO subscription manager. We have seen how to:
- Create client profiles and ACLs in the Solace CLI
- Create and consume Map messages
- Implement the request/reply pattern
- Make subscriptions On-Behalf-Of another program
Please feel free to leave me any comments regarding this example, or leave comments for the entire community to get involved in. If you have any issues sending and receiving a message, check the Solace community QA for answers to common issues seen.