OSGi vs. JRuby on Rails, pt. 2

Posted on December 8, 2008 by Tommy McGuire
Labels: eclipse virgo, software development, ruby, osgi, java
I am writing a note on our our of JRuby on Rails applications to an OSGi, SpringSource dm Server based platform. This is the second section; the first was a brief introduction to the technology and described the motivation. This section describes the process of creating and deploying the web application as a OSGi/dmS bundle. Futher segments will get into a nice technique for supporting Rails development.

Creating and deploying the web archive

Creating the .war file is simple, using warbler. However, it simply calls jar to package up the final contents, with no special arguments. I created a patch to warbler to allow a MANIFEST.MF file to be specified in the warble.rb:

diff --git a/generators/warble/templates/warble.rb b/generators/warble/templates/warble.rb
index 8fcfe99..55f13e9 100644
--- a/generators/warble/templates/warble.rb
+++ b/generators/warble/templates/warble.rb
@@ -59,6 +59,10 @@ Warbler::Config.new do |config|
# config.war_name = "mywar"

+ # Name of the MANIFEST.MF template for the war file. Defaults to the MANIFEST.MF normally generated
+ # by jar -cf....
+ # config.manifest_file = "config/MANIFEST.MF"
# Value of RAILS_ENV for the webapp -- default as shown below
# config.webxml.rails.env = ENV['RAILS_ENV'] || 'production'

@@ -79,4 +83,4 @@ Warbler::Config.new do |config|

# JNDI data source name
# config.webxml.jndi = 'jdbc/rails'
\ No newline at end of file
diff --git a/lib/warbler/config.rb b/lib/warbler/config.rb
index 29f05c8..ee16d91 100644
--- a/lib/warbler/config.rb
+++ b/lib/warbler/config.rb
@@ -59,6 +59,10 @@ module Warbler
# the Rails application
attr_accessor :war_name

+ # Name of the MANIFEST.MF template. Defaults to the MANIFEST.MF normally generated
+ # by jar -cf....
+ attr_accessor :manifest_file
# Extra configuration for web.xml. Controls how the dynamically-generated web.xml
# file is generated.
@@ -229,4 +233,4 @@ module Warbler
"No value for '#@key' found"
\ No newline at end of file
diff --git a/lib/warbler/task.rb b/lib/warbler/task.rb
index 250cdbe..876f5e3 100644
--- a/lib/warbler/task.rb
+++ b/lib/warbler/task.rb
@@ -172,7 +172,11 @@ module Warbler
task "jar" do
war_path = "#{config.war_name}.war"
war_path = File.join(config.autodeploy_dir, war_path) if config.autodeploy_dir
- sh "jar cf #{war_path} -C #{config.staging_dir} ."
+ if config.manifest_file
+ sh "jar cfm #{war_path} #{config.manifest_file} -C #{config.staging_dir} ."
+ else
+ sh "jar cf #{war_path} -C #{config.staging_dir} ."
+ end
@@ -331,4 +335,4 @@ module Warbler
\ No newline at end of file

Metadata: Basic OSGi, dmS, and package import headers

With that change, magic OSGi pixie dust can be sprinkled all over the generated .war file by simply adding the required elements to the config/MANIFEST.MF file. The basic elements needed are:

Bundle-ManifestVersion: 2
Bundle-Name: My Application
Bundle-SymbolicName: MyApp
Bundle-Version: 0.0.0

The Bundle-ManifestVersion header tells OSGi that the metadata follows the v4 standard. The Bundle-Name and Bundle-SymbolicName headers provide a human-readable and OSGi-specific name for the .war file, or in OSGi terms, bundle, respectively. Finally, the Bundle-Version specifies the version number of the bundle; the symbolic name and the version number should uniquely describe the bundle in the OSGi runtime system.

There are a couple of additional SpringSource headers which may not be necessary but seem useful:

Module-Type: Web
Web-ContextPath: /myapp

The Module-Type header identifies the type of the application to the dmS system; the only currently supported type is "Web". The Web-ContextPath header supplies the context root of the deployed web application. Use "/" for an application deployed at the root of the server, or some other string to deploy the application below the root.

This parameter can cause a bit of pain, since most Rails applications more or less expect to live at the root of the server's URL space. Rails and the warbler (actually, jruby-rack) does a fair job of handling the context path, but URLs that are not generated by Rails' helpers may not be correct. (And those which are may not be as well, particularly if you are running dmS behind an Apache front end that does URL proxying and munging.)

The modularity capabilities of OSGi are based around Java packages. A bundle can export packages for use by other bundles and import packages from others. The OSGi system uses the package names along with metadata such as version numbers and "uses" clauses to maintain classpace consistency. (For more information, see the OSGi standards, various blogs, and other, somewhat scanty, documentation.)

The basic header for importing packages is Import-Package. When the OSGi system resolves the .war bundle, the javax.xml.namespace and other packages will be linked with the application bundle, allowing the application bundle's classloader to load classes in the package from the exporting bundle:

Import-Package: javax.xml.namespace,

The advantage is that each application does not need to supply its own copy of every jar file it uses. In this case, the web application is using three OSGi services which will be described later and javax.xml.namespace for no particularly good reason.

One difference between Java and JRuby bundles is that the packages used by Java classes can be determined easily by grubbing through the .class files. With JRuby, however, the task would be more difficult. An alternative is to use the Require-Bundle header to import all of the packages exported by a named bundle:

Require-Bundle: sapjco3; bundle-version=3.0.0,
gov.nasa.common.framework.core.api; bundle-version=2.0.0,
jldap; bundle-version=0.0.0,
log4j; bundle-version=1.2.15,
jruby-complete; bundle-version=1.1.5,
jruby-rack; bundle-version=0.9.2

Require-Bundle is more fragile than Import-Package, but given that the Rails bundle is a client-only application and does not export any packages, it seems reasonable to use Require-Bundle.

The example imports packages from SAP's JCo 3 bundle, part of our internal framework, LDAP and Log4j libraries, some assorted others, and two special bundles. Since we have more than one JRuby on Rails application, it makes sense that they should share the JRuby runtime library as well as jruby-rack. The Require-Bundle header imports all of the packages (of which there are quite a few), while a bundle-ized version of JRuby supplies the packages.

I created the bundle for jruby-complete using bnd, based on the following bnd file:

-classpath: ../original/jruby-complete-1.1.5.jar

Bundle-Name: jruby-complete
Bundle-SymbolicName: jruby-complete
Bundle-Version: 1.1.5
Export-Package: org.jruby.*; version=${Bundle-Version}

Private-Package: !org.jruby.*, *

Import-Package: *; resolution:=optional

This produces a bundle which exports all of the org.jruby packages (and which privately includes the other packages contained in the jruby-complete jar file.

The bnd file for jruby-rack is:

-classpath: ../original/jruby-rack-0.9.2.jar

Bundle-SymbolicName: jruby-rack
Bundle-Name: jruby-rack
Bundle-Version: 0.9.2
Import-Package: \
org.jruby.*, \
javax.servlet.*, \
*; resolution:=optional
Export-Package: *; version:=${Bundle-Version}

Both of these were created based on a very incomplete knowledge of the JRuby and jruby-rack internals, so they should probably be taken with a grain of salt. However, they seem to work.

To deploy these two bundles, I placed them in the repository/bundles/usr directory, where they are automatically loaded by dmS when required by the application bundle.
active directory applied formal logic ashurbanipal authentication books c c++ comics conference continuations coq data structure digital humanities Dijkstra eclipse virgo electronics emacs goodreads haskell http java job Knuth ldap link linux lisp math naming nimrod notation OpenAM osgi parsing pony programming language protocols python quote quotes R random REST ruby rust SAML scala scheme shell software development system administration theory tip toy problems unix vmware yeti
Member of The Internet Defense League
Site proudly generated by Hakyll.