Thursday, December 11, 2008

JRuby already has OSGi metadata

I just noticed that the jruby.jar created by the default ant tasks already has the OSGi metadata. Unfortunately, the jruby-complete jar (needed by warble and the JRuby on Rails .war files) does not.

According to the bug, the metadata was needed for Glassfish v3.

[I just packed off a patch to JRuby's build.xml to jruby-user to OSGify jruby-complete "the same way" as jruby.jar. (Is that too many senses of "to" in less than two sentences?)]

Monday, December 8, 2008

OSGi vs. JRuby on Rails, pt. 2

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|
# of RAILS_ROOT
# 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'
-end
\ No newline at end of file
+end
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"
end
end
-end
\ No newline at end of file
+end
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
end
end
end
@@ -331,4 +335,4 @@ module Warbler
file
end
end
-end
\ No newline at end of file
+end


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,
org.osgi.framework,
org.osgi.service.cm,
org.osgi.util.tracker


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.

Sunday, December 7, 2008

OSGi vs. JRuby on Rails, pt. 1

I am writing a note on our our of JRuby on Rails applications to an OSGi, SpringSource dm Server based platform. This is the first, introductory section; futher segments get into how to configure the Rails app for the OSGi environment and a nice technique for supporting Rails development.

There are more than a few ways of deploying a Ruby on Rails web application. One way of narrowing the choices is to use JRuby, which also has the advantages of allowing the application to use the large number of Java libraries and of making use of a number of excellent deployment and monitoring options. [1] One of the best options involves deploying JRuby on Rails applications with Glassfish. It is well supported and well documented, and seems to be a pretty nice way to go.

On the other hand, OSGi has been generating a lot of interest in Java enterprise circles. OSGi provides a generalized Java container, a structured environment for deploying and executing Java code. The OSGi container, beyond other Java containers, supports modularity and the disciplined use of modules in separate applications. Further, and a unique (to my knowledge) feature of OSGi, the OSGi container can use multiple versions of a module simultaneously. As a result, an OSGi system can provide an ideal software ecosystem for an organization developing and deploying multiple related applications on a consistent and reusable underlying framework.

Current versions of Glassfish and other Java application platforms do not make use of OSGi although they will in the future, to a greater or lesser extent. [2] However, the SpringSource dm Server does. In fact, it does so particularly nicely: a deployed web application is treated as just another OSGi bundle: it can use its choice of other modules, it can access the OSGi services registry, and multiple versions are supported easily.

Our current framework is based on the SpringSource dm Server and a locally written framework for configuration, services, and logging. (The SpringSource dm Server is known here as SSdmS, which is just lame, or dmS, which is shorter but still lame. Is there a good short form of that monstrosity?)

Internally, dmS uses a large number of open source systems, such as Equinox, AspectJ, Log4j, and most importantly for us, Tomcat. JRuby on Rails applications can easily be deployed to Tomcat using Warbler to pack the Rails application as a .war. The remainder of this document attempt to describe how we are handling the deployment and configuration of the Rails applications, and some some additional support we have for application development.

Footnotes

[1] It also may or may not perform better than MRI. Some have reported that the performance is better or much better. My own observation is that the performance is not worse, and might be better.

[2] I have yet to see how a Glassfish application, such as a Rails web app, would interact with OSGi. According to what I have read, Glassfish does not intend to automatically deploy .war files as OSGi bundles, as dmS does. On the other hand, it seems impossible for them not to, since that would be a significant limitation. Other OSGi-based application platforms such as JOnAS do, although in different ways.