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];
}
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.