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...