Home > grails, osgi > Grails on OSGi (2): First steps

Grails on OSGi (2): First steps

The first step on the road to running Grails applications in an OSGi environment is to get a simple application to run as a monolithic bundle (almost) without changes.


Test application

We create a simple Grails application (e.g. the one described in a article on developer works; the application has a single domain class Trip, the controller and all views are generated). A single domain class and the corresponding controller is all we need for a simple test.


Bundle 101

For the first attempt, the Grails application will be transformed, into a single, monolithic bundle (see part 1 for possible modularization levels) containing all dependencies like Grails, Groovy, Hibernate, Spring, …

A bundle is basically a JAR or WAR with some special headers in its manifest file. Required headers are Bundle-SymbolicName, which uniquely identifies the bundle, Bundle-Version, which specifies the bundle version (multiple versions of the bundle can run concurrently!) and Bundle-ClassPath, which specifies the classpath within the bundle. Other important headers are Import-Package and Export-Package, which are necessary when using code and resources from and providing classes to other bundles.


Preparing the bundle

In order to create a valid bundle manifest, $GRAILS_HOME/scripts/_GrailsWar.groovy must be patched with bundle-manifest.patch:

cd $GRAILS_HOME
patch -p0 < /pacth/to/bundle-manifest.patch

In the future, the changes could either be integrated into Grails directly or into a OSGi plugin.

Our Grails application will need to be transformed into a bundle, so we package it using grails war. The application and all dependencies are contained in the resulting WAR file.

The bundle manifest can now be inspected using the following command (on Linux or Mac OSX):

unzip -p target/trip-planner-0.1.war META-INF/MANIFEST.MF

The manifest now looks like this:


Manifest-Version: 1.0
Ant-Version: Apache Ant 1.7.1
Created-By: 14.1-b02-92 (Apple Inc.)
Bundle-ManifestVersion: 2
Bundle-Name: trip-planner
Bundle-SymbolicName: trip-planner
Bundle-Version: 0.1
Bundle-ClassPath: WEB-INF/classes,WEB-INF/lib/antlr-2.7.6.jar,WEB-INF/
lib/aopalliance-1.0.jar,WEB-INF/lib/aspectjrt-1.6.2.jar,WEB-INF/lib/a
spectjweaver-1.6.2.jar,WEB-INF/lib/cglib-nodep-2.1_3.jar,WEB-INF/lib/
commons-beanutils-1.8.0.jar,WEB-INF/lib/commons-codec-1.3.jar,WEB-INF
/lib/commons-collections-3.2.1.jar,WEB-INF/lib/commons-dbcp-1.2.2.jar
,WEB-INF/lib/commons-el-1.0.jar,WEB-INF/lib/commons-fileupload-1.2.1.
jar,WEB-INF/lib/commons-io-1.4.jar,WEB-INF/lib/commons-lang-2.4.jar,W
EB-INF/lib/commons-pool-1.5.3.jar,WEB-INF/lib/commons-validator-1.3.1
.jar,WEB-INF/lib/dom4j-1.6.1.jar,WEB-INF/lib/ehcache-1.6.1.jar,WEB-IN
F/lib/ejb3-persistence-1.0.2.GA.jar,WEB-INF/lib/grails-bootstrap-1.2-
M4.jar,WEB-INF/lib/grails-core-1.2-M4.jar,WEB-INF/lib/grails-crud-1.2
-M4.jar,WEB-INF/lib/grails-docs-1.2-M4.jar,WEB-INF/lib/grails-gorm-1.
2-M4.jar,WEB-INF/lib/grails-resources-1.2-M4.jar,WEB-INF/lib/grails-s
pring-1.2-M4.jar,WEB-INF/lib/grails-web-1.2-M4.jar,WEB-INF/lib/groovy
-all-1.6.5.jar,WEB-INF/lib/hibernate-annotations-3.4.0.GA.jar,WEB-INF
/lib/hibernate-commons-annotations-3.3.0.ga.jar,WEB-INF/lib/hibernate
-core-3.3.1.GA.jar,WEB-INF/lib/hibernate-ehcache-3.3.1.GA.jar,WEB-INF
/lib/hsqldb-1.8.0.10.jar,WEB-INF/lib/javassist-3.4.GA.jar,WEB-INF/lib
/jcl-over-slf4j-1.5.6.jar,WEB-INF/lib/jta-1.1.jar,WEB-INF/lib/jul-to-
slf4j-1.5.6.jar,WEB-INF/lib/log4j-1.2.15.jar,WEB-INF/lib/org.springfr
amework.aop-3.0.0.RC1.jar,WEB-INF/lib/org.springframework.asm-3.0.0.R
C1.jar,WEB-INF/lib/org.springframework.aspects-3.0.0.RC1.jar,WEB-INF/
lib/org.springframework.beans-3.0.0.RC1.jar,WEB-INF/lib/org.springfra
mework.context-3.0.0.RC1.jar,WEB-INF/lib/org.springframework.context.
support-3.0.0.RC1.jar,WEB-INF/lib/org.springframework.core-3.0.0.RC1.
jar,WEB-INF/lib/org.springframework.expression-3.0.0.RC1.jar,WEB-INF/
lib/org.springframework.instrument-3.0.0.RC1.jar,WEB-INF/lib/org.spri
ngframework.jdbc-3.0.0.RC1.jar,WEB-INF/lib/org.springframework.jms-3.
0.0.RC1.jar,WEB-INF/lib/org.springframework.orm-3.0.0.RC1.jar,WEB-INF
/lib/org.springframework.oxm-3.0.0.RC1.jar,WEB-INF/lib/org.springfram
ework.transaction-3.0.0.RC1.jar,WEB-INF/lib/org.springframework.web-3
.0.0.RC1.jar,WEB-INF/lib/org.springframework.web.servlet-3.0.0.RC1.ja
r,WEB-INF/lib/oro-2.0.8.jar,WEB-INF/lib/oscache-2.4.1.jar,WEB-INF/lib
/sitemesh-2.4.jar,WEB-INF/lib/slf4j-api-1.5.6.jar,WEB-INF/lib/slf4j-l
og4j12-1.5.6.jar,WEB-INF/lib/standard-1.1.2.jar,WEB-INF/lib/xpp3_min-
1.1.3.4.O.jar
Import-Package: javax.servlet,javax.servlet.http,javax.servlet.resourc
es,javax.servlet.jsp.resources;resolution:=optional,javax.xml.parsers
,org.w3c.dom,org.xml.sax,org.xml.sax.ext,org.xml.sax.helpers
Webapp-Context: trip-planner

Name: Grails Application
Implementation-Title: trip-planner
Implementation-Version: 0.1
Grails-Version: 1.2-M4

Note that Bundle-ClassPath contains all dependencies required by Grails, JAR files from the lib/ directory, and those contributed by installed Grails plugins.

The bundle imports some packages from the Servlet specification. This is to ensure, that the bundle uses the same classes as the web container, otherwise the classes would not be compatible, as they would be loaded by different class loaders.

BTW: if you need to have a look at the generated web.xml, you can achieve it like this:

unzip -p target/trip-planner-0.1.war WEB-INF/web.xml | xmllint --format -


Running the bundle

In order to run the bundle, it must be installed into an OSGi container like Equinox (the base for the Eclipse IDE), Apache Felix, or Knopflerfish. The test environment described below uses Equinox. Other frameworks could be used as well, but I haven't tested them yet.

Additionally we need a web container like Tomcat or Jetty. The web container needs also to be installed within the OSGi container, so we skip the download on the main site(s), but go looking for OSGI-fied versions. Jetty is OSGi-capable out of the box, an adapted Tomcat also used in Spring dm server can be found at the SpringSource Enterprise Bundle Repository. (Note: Tomcat does not yet work, so we currently use Jetty. I get no errors, but all requests seem to get silently ignored.)

The remaining part is a module usually called Web Extender, which bridges the gap between web applications and the web container. The web extender looks for bundles matching a certain criteria (e.g. existance of WEB-INF/web.xml or some bundle headers) and configures these as a web application. Candidates are Pax Web and Spring-DM.

As Pax Web is currently not able to configure a Grails applications because of PAXWEB-148, and Spring-DM will probably be required for other OSGi and Spring related stuff anyway, we use the latter as Web Extender.


Test environment

The test environment is made up of the excellent Pax Runner with a set of configuration files. Pax Runner is basically a starter for OSGi applications. It supports different OSGi frameworks and provisions both framework and bundles from sources like Maven repos, web servers, or directories.

The test environment loads the Equinox OSGi Framework (version 3.5.1), Spring-DM 2.0M1, Spring 3.0RC1, and Jetty (Tomcat doesn’t work right know). All configuration files be found on GitHub.

The test environment can be installed using the following commands:

Create a directory and change into it:

mkdir testenv; cd testenv

Download Pax Runner, extract it and create a version agnostic link:

curl -o pax-runner-assembly-1.3.0-jdk15.tar.gz http://repo1.maven.org/maven2/org/ops4j/pax/runner/pax-runner-assembly/1.3.0/pax-runner-assembly-1.3.0-jdk15.tar.gz

tar xzf pax-runner-assembly-1.3.0-jdk15.tar.gz

ln -s pax-runner-1.3.0 pax-runner

Get the example configuration:

git clone git://github.com/jetztgradnet/grails-osgi-testenv.git

The framework can be started with the following command:

cd grails-osgi-testenv
./pax-osgitest

After lots of log messages, we arrive at a prompt of the command shell. Help is available using help.

We now type ss to get an overview over all running bundles:


osgi> ss

Framework is launched.

id State Bundle
0 ACTIVE org.eclipse.osgi_3.5.1.R35x_v20090827
1 ACTIVE org.eclipse.osgi.util_3.2.0.v20090520-1800
2 ACTIVE org.eclipse.osgi.services_3.2.0.v20090520-1800
3 ACTIVE org.springframework.aop_3.0.0.RC1
4 ACTIVE org.springframework.asm_3.0.0.RC1
5 ACTIVE org.springframework.aspects_3.0.0.RC1
6 ACTIVE org.springframework.beans_3.0.0.RC1
7 ACTIVE org.springframework.context_3.0.0.RC1
8 ACTIVE org.springframework.context.support_3.0.0.RC1
9 ACTIVE org.springframework.core_3.0.0.RC1
10 ACTIVE org.springframework.expression_3.0.0.RC1
11 ACTIVE org.springframework.jdbc_3.0.0.RC1
12 INSTALLED org.springframework.jms_3.0.0.RC1
13 ACTIVE org.springframework.orm_3.0.0.RC1
14 ACTIVE org.springframework.transaction_3.0.0.RC1
15 ACTIVE org.springframework.web_3.0.0.RC1
16 ACTIVE org.springframework.web.servlet_3.0.0.RC1
17 ACTIVE com.springsource.org.aopalliance_1.0.0
18 ACTIVE com.springsource.org.objectweb.asm_2.2.3
19 ACTIVE com.springsource.net.sf.cglib_2.1.3
20 ACTIVE org.springframework.osgi.core_2.0.0.M1
21 ACTIVE org.springframework.osgi.extender_2.0.0.M1
22 ACTIVE org.springframework.osgi.io_2.0.0.M1
23 ACTIVE org.springframework.osgi.web_2.0.0.M1
24 ACTIVE org.springframework.osgi.web.extender_2.0.0.M1
Fragments=28
25 ACTIVE com.springsource.org.mortbay.jetty.server_6.1.9
26 ACTIVE com.springsource.org.mortbay.util_6.1.9
27 ACTIVE org.springframework.osgi.jetty.start.osgi_1.0.0
28 RESOLVED org.springframework.osgi.jetty.web.extender.fragment.osgi_1.0.1
Master=24
29 ACTIVE com.springsource.javax.activation_1.1.1
30 ACTIVE com.springsource.javax.annotation_1.0.0
31 ACTIVE com.springsource.javax.el_1.0.0
32 ACTIVE com.springsource.javax.ejb_3.0.0
33 ACTIVE com.springsource.javax.mail_1.4.1
34 ACTIVE com.springsource.javax.persistence_1.99.0
35 ACTIVE com.springsource.javax.transaction_1.1.0
36 ACTIVE com.springsource.javax.servlet_2.5.0
37 ACTIVE com.springsource.javax.servlet.jsp_2.1.0
38 ACTIVE com.springsource.javax.xml.bind_2.1.7
39 ACTIVE com.springsource.javax.xml.rpc_1.1.0
40 ACTIVE com.springsource.javax.xml.soap_1.3.0
41 ACTIVE com.springsource.javax.xml.stream_1.0.1
42 ACTIVE com.springsource.javax.xml.ws_2.1.1
43 ACTIVE org.apache.felix.webconsole_2.0.2
45 ACTIVE org.ops4j.pax.logging.pax-logging-api_1.4.0
46 ACTIVE org.ops4j.pax.logging.pax-logging-service_1.4.0
47 ACTIVE org.eclipse.equinox.util_1.0.100.v20090520-1800
48 ACTIVE org.eclipse.equinox.ds_1.1.1.R35x_v20090806
49 ACTIVE org.eclipse.equinox.cm_1.0.100.v20090520-1800
50 ACTIVE org.eclipse.equinox.supplement_1.1.0.v20080421-2006
51 ACTIVE org.eclipse.equinox.common_3.4.0.v20080421-2006
52 ACTIVE org.eclipse.equinox.preferences_3.2.201.R34x_v20080709
53 ACTIVE org.ops4j.pax.url.assembly_1.1.1
54 ACTIVE org.ops4j.pax.url.classpath_1.1.1
55 ACTIVE org.ops4j.pax.url.cache_1.1.1
56 ACTIVE org.ops4j.pax.url.mvn_1.1.1
57 ACTIVE org.ops4j.pax.url.link_1.1.1
58 ACTIVE org.ops4j.pax.url.war_1.1.1
59 ACTIVE org.ops4j.pax.url.wrap_1.1.1

We can now install our bundle:

install file:/path/to/trip-planner-0.1.war
Bundle id is 60

After installing we run ss again. Our bundle is now installed and in the RESOLVED state. With the id of our bundle (the number in the first column), we can now start it:

start 60

The application should now be reachable at http://localhost:8080/trip-planner-0.1/. If you get a 404 HTTP error, wait a moment, to give the web extender time to configure the bundle.

The system directory serves as the current directory of the OSGi container. The HSQLdb files from our test application will be created there, log messages of the container are written to the system/logs directory.

If our web bundle has been changed, we can simply update it using update 60. If this does not work, we can un- and reinstall it using uninstall 60 and the installation procedure described above.


Next steps

As outlined in part 1, the ultimate goal is to create modular Grails applications. The next step will be to extract the dependencies from the monolithic bundle and put them into their own bundles. Spring and Groovy are already available as bundles, other dependencies may need to be replaced with an OSGi-ready version, e.g. from the SpringSource Bundle Repository.

There will be some issues regarding singletons (e.g. ApplicationHolder, ConfigurationHolder) and class loading (Hibernate), but I think, they can be resolved with some work.

The findings may finally find their way into Grails or a Grails OSGi plugin.

Stay tuned...

Fork me on GitHub
Categories: grails, osgi Tags:
  1. December 24th, 2009 at 08:33 | #1

    Good stuff. Looking forward to the next installment. I do think there is a challenge there as from the little I know it seems Grails is not tailored for creation of modular apps (regardless one is using OSGi or not).

  2. July 1st, 2010 at 23:57 | #2

    Y, I agree. Grails inside OSGi makes a good deal of sense. I’ll try out your package as soon as possible.

    Best, M.

  3. Arian
    February 9th, 2011 at 22:12 | #3

    It looks awesome but I can’t make it run. Does it work with grails 1.3.6?

  4. February 10th, 2011 at 00:40 | #4

    Hi Arian,

    hang on, I’ll check with Grails 1.3.6. This is probably due to some dependencies, which are not removed from the WAR file.

    Wolfgang

  5. February 10th, 2011 at 01:18 | #5

    Hi Arian,

    could you try to replace _Event.groovy from the OSGi plugin with the file found here? Simply overwrite it at you Grails plugin directory, e.g. something like ~/.grails/1.3.6/projects/osgitest/plugins/osgi-0.2.1/scripts/_Events.groovy.

    Please give me feedback, if this works for you.

    Regards,

    Wolfgang

  6. Arian
    February 10th, 2011 at 14:26 | #6

    Thanks Wolfgang, I’ll replace _Event.groovy right now.
    I also noticed that bundle-manifest.patch doesn’t seem to work with the 1.3.6 release. I was studying the changes that still need to be made on _GrailsWar.groovy.

    target “war” looks fine on grails 1.3.6, except the part related to resourceList that still needs to be changed.

    target(createDescriptor:”Creates the WEB-INF/grails.xml file used to load Grails classes in WAR mode”) {
    - def resourceList = pluginSettings.getArtefactResources()
    + def resourceList = GrailsResourceLoaderHolder.resourceLoader.getResources()

  7. February 10th, 2011 at 16:18 | #7

    Hi Arian,

    will have a look at it tonight or tomorrow.

    There are some more required changes, i.e. allow regex when replacing libs in _Events.groovy (so they don’t depend on single versions). Otherwise this breaks with every new dependency or version change.
    I also plan to replace parts of _Osgi.groovy with my groovyx.osgi module and replace Spring DM with Eclipse Gemini Blueprint. But this takes some more time.

    Wolfgang

  1. November 25th, 2009 at 09:48 | #1