JRuby JMS as a replacement for ActiveMessaging

I was talking to JRuby committer Ola Bini at RailsConf a few weeks ago about our ActiveMessaging solution, and he suggested we try using JRuby and JMS instead. By using JMS, we can eliminate the poller and have a truly “Event Driven” solution. So I’ve been playing around with JRuby and JMS in my spare time, with some really interesting results. In JRuby, we can implement the Java JMS interface MessageListener:

require java

include_class org.apache.activemq.ActiveMQConnectionFactory
include_class org.apache.activemq.util.ByteSequence
include_class org.apache.activemq.command.ActiveMQBytesMessage
include_class javax.jms.MessageListener

ENV[RAILS_ENV] = development
RAILS_ROOT=File.expand_path(File.join(File.dirname(__FILE__), ..,..))
load File.join(RAILS_ROOT, config, environment.rb)

class MessageHandler
  include javax.jms.MessageListener

  def onMessage(serialized_message)
    message_body = serialized_messageed_message.get_content.get_data.inject() { |body, byte| body << byte }
    customer_payload = YAML.load(message_body)
    customer = Customer.new(:name => customer_payload.name)
    customer.id = customer_payload.id
    customer.save!
  end

  def run
    factory = ActiveMQConnectionFactory.new(tcp://localhost:61616)
    connection = factory.create_connection();
    session = connection.create_session(false, Session::AUTO_ACKNOWLEDGE);
    queue = session.create_queue(Customer);

    consumer = session.create_consumer(queue);
    consumer.set_message_listener(self);

    connection.start();
    puts Listening…
  end
end

handler = MessageHandler.new
handler.run

Contrast this with similar Java code:

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.util.ByteSequence;
import org.apache.activemq.command.ActiveMQBytesMessage;

import javax.jms.*;

public class MessageHandler implements MessageListener {

    private Connection connection;
    private Session session;
    private Queue queue;

    public void onMessage(Message serializedMessage) {
        String message = ;
        ActiveMQBytesMessage bytes_message = (ActiveMQBytesMessage)serializedMessage;
        ByteSequence sequence = bytes_message.getContent();

        for(int i=0; i < sequence.getData().length; i++) {
            message += (char)sequence.getData()[i];
        }

        // Here is where you would use the message to create the Customer object
        // For now I will just print the YAML

        System.out.println(message);
    }

    public static void main(String[] args) throws JMSException {
        MessageHandler handler = new MessageHandler();
        handler.run();
    }

    private void run() throws JMSException {
        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(tcp://localhost:61616);
        connection = factory.createConnection();
        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        queue = session.createQueue(Customer);

        MessageConsumer consumer = session.createConsumer(queue);
        consumer.setMessageListener(this);

        connection.start();
        System.out.println(Listening…);
    }
}

You’ll notice that the ‘run’ methods are identical, save for the “rubyisation” of the Java code (underscores and the like). Java requires you to implement the onMessage method when inheriting from the MessageListener class, so we have done that in JRuby.

The Java class definition:

public class MessageHandler implements MessageListener

is replaced with the JRuby equivalent:

class MessageHandler
  include javax.jms.MessageListener

The rest of the JRuby code is very similar to its Java equivalent, though it was tough going back to the type-safety gymnastics of Java, I must admit.

I was surprised that with the version of JRuby I am running (ruby 1.8.5 (2007-06-02 rev 3812) [i386-jruby1.0.0RC3]), I had to use camel case for the onMessage method definition rather then ruby-like underscores, but it’s no big deal.

We’ll save this file under the processors directory of our Orders application as message_handler.rb

Then we can deploy the Orders application in JRuby. Note that this isn’t strictly necessary (but more on that later).

From the instructions on the JRuby wiki:

Install the Goldspike plugin:
~/orders]>script/plugin install svn://rubyforge.org/var/svn/jruby-extras/trunk/rails-integration/plugins/goldspike

Install the ActiveRecord-JDBC gem:
~/orders]>gem install activerecord-jdbc –no-rdoc –no-ri

The plugin provides the following rake task:
war:standalone:create - which packages up a web archive containing your application along with JRuby and the rails libraries

Run the task, and deploy the war to your app server (I’m using Tomcat)

We can then make the apache-activemq-4.1.1.jar available to our script for the jms-related code by setting the CLASSPATH environment variable:

export CLASSPATH=/path_to_jar/apache-activemqvemq-4.1.1.jar:$CLASSPATH

Then all we need to do is run the JRuby code with the following command:

jruby /path_to_tomcat/webapps/orders/processors/message_handler.rb

And that’s it. Now we can process Customer messages using our JRuby JMS implementation.

Comments are closed.