Using the Clojure-based bowerick MoM/JMS Library in Java

First of all, for those who did not run across bowerick yet, here is a brief description: bowerick is a Clojure library that bundles the functionality of various Message-oriented Middleware (MoM)/Java Message Service (JMS) implementations and provides one centralized abstraction for these. It currently supports Stomp, OpenWire, MQTT, and Stomp via WebSockets. You can find more information in other posts I wrote about bowerick.

In this post, I briefly discuss how bowerick, which is written almost entirely in Clojure, can be used in Java. In a sense, this completes a full circle as most (all? But I did not double-check that.) of the MoM/JMS libraries employed in bowerick are Java-based.

My intention is to keep the post focused and concise and to present the most interesting things straight away. Hence, the outline is a bit unusual compared to my other posts:

  • bowerick and Java
    In this part, I present some Java code snippets etc. for using bowerick in Java.
  • bowerick Design Considerations for Java Interoprability
    Here, I write about some measures I took in order to allow/ease Java interoperability.
  • Closing Remarks

bowerick in Java

In order to demonstrate the use of bowerick in Java, I created a simple example project on github. The example project was set up as Maven project in which the examples were implemented as JUnit test cases. In this section, I will present some snippets of this project.

All transports (Stomp, OpenWire, etc.) supported by bowerick in Clojure can also be used in Java. However, as additional measures have to be taken to make Clojure code available in Java, not all Clojure functions are exposed to Java. Still, I think that the most important features are available in Java as well. The bowerick features currently available in Java are:

  • Starting and stopping an embedded JMS broker
  • Creating a producer and sending messages
  • Creating a consumer and receiving messages
  • Selected Serialization Options:
    Default, JSON, Kryo (via Carbonite) with Optional LZF Compression

The central point for using bowerick in Java is the JmsController class. E.g., starting and stopping of an embedded broker can be done via an instance of this class as shown in the following listing:

jmsController = new JmsController("stomp://some-url:port");
jmsController.startEmbeddedBroker();
...
jmsController.stopEmbeddedBroker();

This example shows the start/stop of a broker with a single Stomp transport. In addition, a broker with multiple transports can be created by passing a List of Strings to the JmsController constructor as shown in the following listing:

List<String> urls = Arrays.asList(new String[] {
  "stomp://127.0.0.1:1701",
  "tcp://127.0.0.1:1864",
  "ws://127.0.0.1:8472",
  "mqtt://127.0.0.1:2000"});
jmsController = new JmsController(urls);
jmsController.startEmbeddedBroker();
...
jmsController.stopEmbeddedBroker();

For the creation of consumers and producers, static methods in JmsController are used. In the following, a simple producer using the example of sending a String is shown:

JmsProducer producer = JmsController.createJsonProducer(
  "stomp://127.0.0.1:1701",
  "/topic/test, 1);
producer.sendData("A test string");
producer.close();

Similarly, a consumer for receiving data can be created via a static method in JmsController. The following listing shows the creation and usage of a consumer:

final CountDownLatch cdl = new CountDownLatch(1);
final StringBuffer received = new StringBuffer();

AutoCloseable consumer = JmsController.createJsonConsumer(
  "stomp://127.0.0.1:1701,
  "/topic/test",
  new JmsConsumerCallback() {
    public void processData(Object data) {
      received.append((String) data);
      cdl.countDown();
    }
  },
  1);

cdl.await();
System.out.println(received.toString());

consumer.close();

Note that producers and consumers have to be closed when they are not needed anymore. Producers and consumers implement AutoClosable such that they can also be used, e.g., in corresponding try-catch blocks for auto closing the created instances. Furthermore, the consumer example includes a bit more surrounding code in order to demonstrate the use of the received data.

Instead of Strings, more complex data structures, such as Java lists, maps, or sets, can also be sent via these mechanisms.

Regarding the Maven pom.xml, the parts that have to be added are pretty simple as well:

...
  <dependencies>
    <dependency>
      <groupId>bowerick</groupId>
      <artifactId>bowerick</artifactId>
      <version>1.99.12</version>
    </dependency>
  </dependencies>
...
  <repositories>
    <repository>
      <id>clojars.org</id>
      <url>http://clojars.org/repo</url>
    </repository>
  </repositories>
...

bowerick Design Considerations for Java Interoperability

Even though I primarily use bowerick in Clojure, I also tried to implement it already with Java interoperability in mind. In this section, I will outline the aspects that I consider important in this context.

Clojure offers, in my opinion, excellent Java interoperability such that it is very easy to write Clojure projects that can also be used from within Java. However, in scope of bowerick, I came across some things that I found helpful.

In bowerick, consumers and producers are not just pure functions. In fact, a producer, e.g., has to provide functionality for sending data and functionality for closing and freeing its resources, namely the JMS connection. Hence, there has to be some way for dispatching between these two functions. For bowerick, I chose to define producers and consumers as records and to pass the send and close functions as constructor arguments to these records.

In order to ease the use of these records in Java, I defined the records to implement the Java AutoClosable interface. This is shown for the consumer in the following listing:

(defrecord ConsumerWrapper [close-fn]
  AutoCloseable
    (close [this]
      (close-fn))) 

From the perspective of Clojure, just passing a function to the consumer as callback for processing the data would have been sufficient. However, in order to ease the interoperability with Java, I chose to specifically define an interface for this:

(gen-interface
  :name bowerick.JmsConsumerCallback
  :methods [[processData [Object] void]])

In order to prepare the producer functionality to be available in Java as well, I defined another interface for sending the producer data, in addition to AutoClosable. In the following listing, both the interface definition and the record definition are shown in one listing even though they are defined in separate namespaces in bowerick:

(gen-interface
  :name bowerick.JmsProducer
  :extends [AutoCloseable]
  :methods [[sendData [Object] void]])
...
(defrecord ProducerWrapper [send-fn close-fn]
  AutoCloseable
    (close [this]
      (close-fn))
  JmsProducer
    (sendData [this data]
      (send-fn data))
  IFn
    (invoke [this data]
      (send-fn data)))

Closing Remarks

Personally, I think that this can be an example for a symbiotic co-existence of Java and Clojure. My personal impression is that using Clojure helped me a lot during the development of bowerick and that making bowerick available in Java can benefit on the Java side as well. However, this is just my very subjective impression without any real hard figures to “prove” it.

Regarding the current Java interoperability implementation: I use Object as type in some places, such as the JmsController constructor or the JmsConsumerCallback processData method. For the JmsController, currently supported argument types are, String or List. For the processData method, the type strongly depends on the data that was sent and how it got de-serialized. However, Object being somewhat the NULL pointer equivalent of Java, using just Object breaks the benefits of static typing and requires casting etc., which may be considered disadvantageous.

I hope that this is useful for some. Feedback, comments, and constructive criticism, are always appreciated.

This entry was posted in bowerick, Libs. and tagged , , , , , . Bookmark the permalink.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.