Case_Study_JRubyOnEclipseRCP

donv edited this page Jan 6, 2011 · 4 revisions
Clone this wiki locally

This page will follow a project developing an airport fuelling system. We will try to describe the different components used, and some of the pitfalls detected, but more importantly focus on working examples and best practises.

EVERYONE is allowed to comment, add or edit to this page! The more views the better!

Add your question here:

I wonder...

Table of Contents

Purpose

AIFUDIS offers information about flights at one airport to a human Dispatcher controlling the fuel trucks at the airport. The dispatcher then assigns operators and their trucks to specific flights. The operators then fuel the aircrafts and report progress and the finished transaction back to the central system and the dispatcher.

At the end of the day reports on all transactions and the state of the trucks and fuel tanks are used to send invoices and order more fuel.

Architecture

Dispatcher

  • Eclipse RCP 3.4
  • Java 6
  • JRuby 1.2.0 in the entire application except Application, Activator and Editor and View skeleton classes.
  • Firefox 3
  • ActiveMessaging 0.5.0
  • ActiveSupport 2.3.2
  • ActiveRecord 2.3.2

Operator

  • SWT+JFace 3.4
  • Java 6
  • JRuby 1.2.0 in complete application.
  • ActiveMessaging 0.5.0
  • ActiveSupport 2.3.2
  • ActiveRecord 2.3.2

Administration

  • Ruby on Rails 2.3.2
  • JRuby 1.2.0
  • Java 6
  • PostgreSQL 8.3
  • ActiveMessaging Plugin

Communicator

  • Ruby on Rails 2.3.2
  • JRuby 1.2.0
  • Java 6
  • ActiveMessaging Plugin
  • ActiveMQ 5.2
  • PostgreSQL 8.3

Flight Importer

Polls flights from an Oracle 10 database using ActiveRecord.

  • Ruby on Rails 2.3.2
  • JRuby 1.2.0
  • Java 6
  • ActiveMessaging Plugin
  • ActiveMQ 5.2
  • PostgreSQL 8,3

Progress

1.1.0 2009-05-24 Customer acceptance test and finishing touches

  • Fix bugs and change requests from customer acceptance testing.
  • Handbooks and accompanying documentation :)
  • Improve Nagios monitoring
  • GPS geofencing warning operators that have driven to the wrong gate.
  • Dump of transactions to USB memory stick in case of communications break down.
  • Improve startup time of the system as a whole.

1.0.0 2009-04-27 Last sprint before customer acceptance test

  • 4 cars in production :)
  • Fixed lots of bugs detected in the pilot.
  • Drill down analysis completed.
  • Stock accounting completed.
  • Lots of nice and helpful touches in the GUIs like warnings when trying to dispatch a small vehicle to a 747 :)
  • XML Export of transactions to the supplying oil companies finished.
  • Configurable IO using JRuby FFI finished. Dynamic setting high/low/absent per vehicle IO signal.
  • Controlled shutdown of the system initiated by detecting power down of the vehicle.

0.9.0 2009-03-08 Administration and reporting

  • Reports and drill down analysis is finished.
  • The one vehicle in production gives good feedback on what works, but also draws time from development.
  • JVM Crash! A mystical JVM crash appeared on one terminal. We never found the cause, but a re-installation of OS and application solved it.
  • Added interaction with ActiveMQ JMX MBeans to purge queues automatically when needed. See Accessing JMX

0.8.0 2009-02-08 Improve in-vehicle and dispatcher systems using the experience from actual use

  • Bug fixing after feedback from actual production usage of the pilot vehicle.
  • Practical stuff like user management forces its way into the development line.
  • FFI Integration! We need to read digital sensors in the the vehicles to detect if the hand brake is on or if the side door is open or closed. The entire application must be blocked preventing input while the vehicle is moving, so we open up a modal dialog with a warning image when the hand brake is not on. Using FFI was REALLY EASY, except that it didn't work on the latest stable release, JRuby 1.1.6, so we are back on JRuby trunk on the in-vehicle systems. If you have ever tried using a shared library (.so or .dll) from Java or Ruby using JNI or a Ruby C extension, you should REALLY look at FFI! It has been around for about a year, and is now maturing, but it has been easy to use from the start :)
  • More fuel meter integration. We now send a reset signal to the fuel meter to mark the end of a transaction. This was previously the task of the operators, but they frequently forgot, and transactions had to be corrected manually.
  • More RCP views and editors. We made a neat efficient super class for all lists, so adding the Operator list and Vehicle list took only minutes. We are happy we postponed making the superclass until the third list was made to take advantage of the experience of making the first two.
  • Persisting the state of an RCP editor or view.
  • JFace context menus. Here we miss using blocks to subclass Java classes inline. JFace actions must be written as separate Ruby classes to be able to subclass the Java Action class.
  • JFace table viewer cell editors implemented. Thanks to our in-house SWT expert Arndt Kubosch :)
  • First draft of IATA fuel transactions xml export. We use a pretty straight XML Builder approach.

0.7.0 2009-01-11 Actual production use

  • The first vehicle is running regular production missions!
  • Wireless communication problems have been solved
  • First show of the Dispatch thick GUI client.
  • A nice Christmas vacation :)
  • JRuby 1.1.6 was released, and we are now using it on all tiers.
  • Found bugs in ActiveMQ default persistence, so we swicthed to derby persistence. Found that derby persistence had a maximum message size of 1MB, so we added compression to large messages using deflate.

0.6.0 2008-11-30 Getting things to work

  • JRuby 1.1.5 released with new Java Integration, but it introduced a change in behaviour that broke our Eclipse RCP application. The problem was OSGI class loading, and the solution was to set the context class loader for every thread that wanted to execute JRuby code.
  • Added scraping of basic data like airlines, aircraft types, coutries and airports from Wikipedia. We first did this using Java, the JRuby, and the difference is striking. About 10 to 1 in number of lines :)

0.5.0 2008-11-02

  • Added setting of different payment types: Contract, Carnet Card, Authorization by reference and CASH.
  • Finalized the ticket printed to the airplane captain.
  • Started using GUID (globally unique IDs) for all records sent to the vehicles. Each vehicle has its own Derby (JavaDB) database, and ActiveRecord would generate new ID values for each record sent from the server to the vehicle. Using GUIDs and keeping the same GUID across tiers made the duplicate ID problem go away. We recommend this approach if you need to synchronize data across databases, and still want to use ActiveRecord.

0.4.0 2008-10-03 Complete the vehicle system for production use

  • The system was finished on time, but as always, it takes time for the customer to actually start using it.
  • Started using JIRA to track requirements and issues. Made JIRA accessible for the customer. This has been a success, although getting the customer to enter bug reports and feature requests directly has been difficult.
  • Added logging. We use Log4j on the Java side, and Log4r on the Ruby side. We have not integrated the two, but both log to stdout, and we have no special needs, yet.
  • Introduced the fuel meter simulator with thick GUI. Can both simulate the fuel meter over a serial line, or embedded in the application.
  • Introduced YUI in the administration web application.

0.3.0 2008-09-05 First LIVE transaction

  • We did it! (barely) Our first transaction was generated using the vehicle system integrated to the fuel meter through an RS422 interface, and sent to the server by ActiveMQ and stored in the PostgreSQL database where it can be displayed usign the JRubyOnRails web application.
  • JRuby 1.1.4 was released with lots of nice bug fixes, including some reported by us. A big THANK YOU to the JRuby team! I am impressed by their responsiveness and willingness to fix an issue in direct response to a developer request.
  • JRuby 1.1.4 included some refactorings that broke
  • Derby DB / JavaDB was introduced with ActiveRecord for persisting local data on the vehicle system. This has been a great success and can be recommended for others.
A couple of problems have occurred for advanced use and have been reported in http://jira.codehaus.org/browse/JRUBY-2995 and http://jira.codehaus.org/browse/JRUBY-2949
  • ActiveRecord Migrations introduced for schema handling. A couple of issues reported on this: http://jira.codehaus.org/browse/JRUBY-2996
  • Serial communication using ruby libraries did not work.
    • Reading from /dev/ttyS0 as a file {noformat}File.open('/dev/ttyS0', 'r+'){noformat} gave illegal seek error. This has been reported as http://jira.codehaus.org/browse/JRUBY-2979
    • Reading from /dev/ttyS0 blocks, and there is no time-out mechanism.
    • Timeout.timeout does not work in JRuby, at all.
    • IO.popen returns wrong PID, so trying to put serial communication in a child process and kill it after time-out has passed did not work. This has been reported as http://jira.codehaus.org/browse/JRUBY-2977
  • Final solution to serial communication was to use the Java library RXTX 2.1 (without the javax.comm API). This has worked very well and can be recommended.
  • Introduced an ActiveMQ server on the vehicle system with the in-built bridging component to implement persistent messaging. Messages are stored locally by ActiveMQ until they can be sent to the server. Likewise messages received from the server are stored until the application can receive them. This ActiveMQ server is running inside the same JVM as the main application to simplify start-up/shut-down and monitoring.
  • Printing implemented for the receipt for a transaction. We use an Epson thermal printer with an RS232 interface for reliability and physical robustness. Some interesting pitfalls regarding invisible carriage-returns (\r) in here-docs in the source code that are required by the printer to work :)

0.2.0 2008-08-03 First operator functions

  • Due to customer priorities we started working on the vehicle system. We set up the basic GUI and the integration with the fuel meter.
  • Eclipse RCP was dropped on the Vehicle system since it added complexity, made build and deployment more difficult and we really did not need any of the functionality Eclipse RCP offers. The result is straight JRuby with SWT + JFace + ActiveMQ.
thumb thumb Image:operator_0.2_2.jpg Image:operator_0.2_3.jpg

0.1.0 2008-06-16 Basic models, Working Flight List, Administration of base data.

  • The release went well and the customer is happy! Below is a screen shot of the flight list.
Currently, JRuby is used to fetch data from an external Oracle database, post flight updates to an ActiveMQ topic, handle all administration using a Rails application, and to unmarshal a YAML stream of flight updates on the dispatcher.

Image:dispatcher_0.1.0.jpg

0.0.0 2008-05-15 Infrastructure

  • All the main components of the system are up and running with no functionality. That means all infrastructure is in place. Woohoo!

Using JRuby in an Eclipse RCP Plugin

Package JRuby as a plugin

TODO: can you please provide some input on how to do that?

  • create plugin with PDE, name: org.jruby, version: $JRUBY_VERSION$, I guess
  • use jruby-complete.jar or package the normal jar + the ruby stdlib in a jarred plugin?
  • in the Manifest, do you use Eclipse-BuddyPolicy=dependent to allow other plugins to run their scripts ? (by exposing their classpath to org.jruby)
Thanks, --InGer 06:06, 17 September 2008 (PDT)

Answer: We tried using the existing JRuby eclipse plugin provided by both RDT and Aptana, but they were not updated as quickly as we needed, and we often needed to run on JRuby trunk. The solution for us is to include JRuby in our plugin, and run the plugin unjarred. This also allows us to install gems directly into our copy of JRuby. Thanks, --donv (2009-02-01)

Activators, Editors, views, etc.

When defining plugin activators, editors, views etc., you do this in the plugin.xml file. The classes mentioned here are loaded using reflection. We have not found a way to load ruby classes this way, so all these classes need to be Java classes. This does continually push development towards using Java instead of Ruby.

We would like a way to use Ruby classes directly as Editors, Views, etc. Please comment if you have an idea how to do this.

Comment from DominikM I don't think it would help with activators but for everything that works with the ExtensionRegistry there is a way of getting external non-java implementations to work: IExecutableExtension

See: http://help.eclipse.org/stable/index.jsp?topic=/org.eclipse.platform.doc.isv/reference/api/org/eclipse/core/runtime/IExecutableExtension.html

By implementing something like a JRubyExtensionAdapter which is used in extension points like this: xxx.yyy.JRubyExtensionAdapter:/scripts/views/MyRubyView.rb you could get what you want with only a little generic java code.

Everything that is put after the double-colon is provided to the IExecutableExtension via setInitializationData(). Hope that helps :) Good luck on your project and please continue to update your progress.

Comment from Craig: Have you considered looking at Anton Arhipov's work during the Google Summer of Code 2007? It is called Add the ability to write plugins in jruby or groovy. He uses something like DominikM's suggestion, with a class called ScriptExtensionProxy that implements IExecutableExtension and allows your plugin to define its extension using a syntax like class="org.eclipse.soc.scripting.contributor.proxy.ScriptProxy:org.eclipse.ui.IViewPart/view.rb" (this was cut from an example plugin.xml extension to org.eclipse.ui.views, and says that the class in 'view.rb' implements the java interface org.eclipse.ui.IViewPart, and can be accessed through the IExecutableExtension class ScriptProxy.

Organizing ruby code.

The Eclipse RCP SDK gives good guidelines on how to organize your Java code. A similar guide is needed for organizing the Ruby code. A project specific guide will emerge, but we would like to see if this could be used across projects.

We have a few ideas on where to put the Ruby code:

  • Separate /rubysrc folder.
  • A /src/ruby folder.
  • /lib
  • Together with the Java code that uses the Ruby code. If my.domain.JavaClass uses file my_ruby_class.rb, the the my_ruby_class.rb file id placed together with the JavaClass.java file in the /src/my/domain folder. This enables the Java class to load the Ruby class using Classloader.getResourceAsStream().
If you have any experience with organizing ruby code in a Java project, please share here!

Sending objects over the network

We use ActiveMQ with STOMP as transport for messaging, supported by the excellent ActiveMessaging plugin.

Here is an example of how we send objects over the network. Only the object fields are sent, no code, and we use YAML for this since it is simple, readable, and has parsers on both Java and Ruby platforms. *flight* is an ActiveRecord model.

JRuby sender

  class FlightUpdater
    include ActiveMessaging::MessageSender
    extend ActiveMessaging::MessageSender
  
    QUEUE = :flight_updates
  
    publishes_to QUEUE
  
    def self.update flight
      payload = YAML.dump flight.attributes
      publish(QUEUE, payload)
    end
  
  end

Java receiver

  ...
  jruby.defineReadonlyVariable("$flight", JavaEmbedUtils.javaToRuby(jruby, flight));
  Object map_as_ruby_object = jruby.evalScriptlet("YAML.load($flight)");
  @SuppressWarnings("unchecked")
  Map<Object, Object> flight_attributes = 
          (Map<Object, Object>) JavaEmbedUtils.rubyToJava(jruby, (org.jruby.runtime.builtin.IRubyObject) map_as_ruby_object, Map.class);
  ...

This is a bit more complicated than necessary, but we wanted to try the Ruby-To-Java conversion mechanism. This has later been removed, and we now use a Ruby receiver:

Ruby receiver

  class FlightUpdatesProcessor < ActiveMessaging::Processor
    subscribes_to :flight_updates
  
    def on_message(message)
      flight = YAML.load message
    end  
  end

Please leave a comment if you would like more details.

JRuby FFI

One important feature of the in-vehicle systems are to detect what the operator is doing, and report this to the dispatcher. Also, the in-vehicle must disable input when the vehicle is moving to avoid collisions. To do this, we need to read digital sensors in the vehicle. On other projects we have used JNI or Ruby C Extensions to talk to shared libraries (.so or .dll), but in 2008 FFI was introduced. FFI, the Foreign Function Interface, was first designed and implemented by the Rubinius project, inspired by the FFI package for Python. During 2008 FFI was implemented in JRuby and MRI and is now the only integration package available on all Ruby implementations (except RED :) )

Using FFI to call a function in our shared library looks like this:

  require 'ffi'
  module SMCIO
    extend FFI::Library
    ffi_lib 'smc_io.so'
    attach_function :smc_io_GetDigInStatus, [:ulong, :pointer], :ulong
  end

That's it! Note that accessing libraries with names not starting with 'lib' is supported as of JRuby 1.2.0. We hit a problem with threads and FFI. When declaring the FFI function in one thread, and using it in another, the function would return an error code. Declaring and using the FFi function in the same thread fixed the problem. We have no explanation for this behavior, yet.

This works:

  Thread.start do
    require 'ffi'
    module SMCIO
      extend FFI::Library
      ffi_lib 'smc_io.so'
      attach_function :smc_io_GetDigInStatus, [:ulong, :pointer], :ulong
    end
    res = MemoryPointer.new :ulong
    SMCIO.smc_io_GetDigInStatus(0x000f, res)
    i = res.get_ulong(0)
  end

This does not:

  require 'ffi'
  module SMCIO
    extend FFI::Library
    ffi_lib 'smc_io.so'
    attach_function :smc_io_GetDigInStatus, [:ulong, :pointer], :ulong
  end
  Thread.start do
    res = MemoryPointer.new :ulong
    SMCIO.smc_io_GetDigInStatus(0x000f, res)
    i = res.get_ulong(0)
  end

This turned out to be a limitation of the C library used. It stores information in thread local storage during initialization. Nothing to do with JRuby or FFI.

Compressing messages

When messages became large, over 1MB, message persistence started to cause trouble, so we added compression using deflate:

  require 'zlib'
  ...
  deflated_message = Zlib::Deflate.deflate(message,9)
  ...
  require 'zlib'
  ...
  def on_message(deflated_message)
    message = Zlib::Inflate.inflate(deflated_message)
  ...