springOSGi May 20, 2007Posted by Bernd in equinox, spring, springOSGi.
A few weeks ago, the springframework released the 2nd milestone of the spring-OSGi bridge.
In my free time over the last two weeks I ported Jürgen Höllers jPetStore example to springOsgi based on M2 (more or less, I still had some issues with broken manifests), Eclipse Equinox (with a Jetty running inside) iBATIS and HSQLDB in the backend and Spring WEB-MVC at the frontend.
First of all, I needed to find a way to get springOSGi into Eclipse. The thrilling thing there has been to find a possibility to debug the spring and the springOSGi bridge… If you just copy the bundles into your target, Eclipse is not able to find the source code. Finally I got the solution. For me it worked best use the “Import Plug-ins and Fragments” Wizard to import the bundles as binary projects into my workspace. Now I was able to attach the source code. However, I don’t really like the way springOSGi is distributed. If you have a look to the download, you’ll find a folder lib which contains all the libraries, including spring itself, coming with springOSGi. But you don’t find the jars in there, but a whole bunch of folders: one for each library. Inside such a folder again you don’t find the jars, but another folder. Inside this folder you finally get the jar. This structure seems to come from the maven build.
The easiest thing you can do if you are using the same setup as me is to just search for all jars in the download and copy them to one location. Then use this location for your import.
Alin Dreghiciu has reported another solution just some days ago. This also seams to work very well.
Analyzing the example
When I started, I’d just wanted to see how well the current bridge already was and what I had to do to refactor a running spring application. As an example I have chosen the jPetStore example which comes with the official spring distribution. jPetStore is a simple web-App with a database in the backend. Additionally it uses spring-remoting for the communication with another client.
For me, this example has been mainly a technical prototype. So I decided to create a bundle for each of the technical layers (database, domain-logic and web) and one for the business objects.
I started with the database layer. The example uses iBATIS and apache.commons.dbcp. As I wanted to change as little as possible at the example I decided to use the same libraries. This was a pretty easy task. The stumbling blocks here have been the broken manifest of commons.collections which came with the official distribution (org.springframework.osgi.commons-collections.osgi has a broken manifest) and the fact that BasicDatasource from DBCP does not respect the context classloader.
As iBATIS has not been in the set of libs that came with the download I had to create a bundle for that on my own. Using the PDE-Project-Creation-Wizard this is really easy. The only thing I added to the generated Manifest was the Eclipse-BuddyPolicy (Eclipse-BuddyPolicy: registered). The only registering bundle is bundle containing the domain-objects. This has also been the only point where I used non-osgi standard mechanisms from equinox.
When running the example the first time, I had to make sure, that the bundle with the symbolic name org.springframework.osgi.spring-osgi-extender has a lower startlevel than my application bundles. Otherwise the spring application contexts of my bundles might not be loaded.
The next layer has been the middle tier. Nothing interesting about the business logic, but this layer also includes some http-based service exporters. Milestone 2 has no support for that. So I had to implement some utilities to make the spring-web-support working in OSGi.
The main issue here has been the locating and loading of resources. All of that has been factored out to a utility bundle to keep it reusable.
Another problem has been the process of registering servlets and resources. I decided to use the OSGi HTTP service implementation by Equinox which uses Jetty as servletcontainer. (Some useful utilities are implemented in the org.eclipse.equinox.http.helper bundle which can be checked out from the eclipse repository (Server: :pserver:firstname.lastname@example.org:/cvsroot/eclipse Module: /equinox-incubator/org.eclipse.equinox.http.helper))
In order to get this service I used a ServiceTracker. As in springOSGi each bundle has its own ApplicationContext, the ApplicationContext of the bundle is also needed to create the context for the web application.In a first version I implemented the servlet-registration in the bundles BundleActivator, but there the only possibility to get the ApplicationContext is to import the ApplicationContext as an OSGi service. According to the spec, this is not the way to go (The ApplicationContext you get is from a specific bundle, but you don’t know from which version of the bundle). Another problem with this implementation was the timing: the ApplicationContext is exported as a service after the start() method of the BundleActivator was executed. Hence it is not possible to register the http stuff in the start method. It has to be done asynchronously. So this was a hack.
In the second version I have created a little utility which implements ApplicationContextAware and BundleContextAware. This utility has to be added to the application context of the bundle. From that utility you can get a callback after the ApplicationContext and the BundleContext/HttpService has been set. Additionally I had to add several 3rd party libs and exchanged some of the libs which have been included in M2 because some packages have not been included in the libs. (Missing packages in several bundles)
Last but not least, Jürgen used some springAOP for transactions. After creating a bundle for aspectj which contains the aspectjweaver.jar and adding dependencies to the respective packages this also worked out of the box :-)
After all the registration problems have been solved, I continued with the web layer. Here, the exciting question has been what has to be done to support spring webMVC and JSPs. Equinox comes with a JSPServlet which uses jasper internally to render the JSPs. So this was a really easy job. While adding the dependencies, I noticed that spring comes with a jstl bundle, but does not include the apache commons standard taglibs. This causes a cyclic dependency. As standard taglibs need jstl and vice versa. So I created my own bundle for that and opened this bug org.springframework.osgi.jstl.osgi should include standard.jar.
Now I had to fight a bit with the internals of jasper and see how it resolves (or better tries to resolve) the TagLib definitions. After some debugging I finally noticed that the only possibility for me to place tld-files was a folder called WEB-INF relative to the web-context-path you had to specify when registering the equinox JSP-Servlet ( I used
JspServlet servlet = new JspServlet(context.getBundle(), "/web");
so the Taglib definitions had to be in a folder /web/WEB-INF relative to the bundles root.)
After sailing around this last rock Jürgens jPetStore finally worked :-) It has not always been an easy job, but given the fact that it is a pre-1.0-release it was pretty straight-forward after the initial installation problems. Most of the problems I had have just been broken manifests and missing dependencies in the libraries coming with the springOSGi bridge.
If you are interested, I have uploaded my workspace to http://www.kolbware.de/springOsgi/jPetStoreOSGi.zip. It contains all the necessary bundles including equinox as OSGi platform. The de.kolbware.jpetstore.launcher project contains launchconfigurations for HSQLBD and the web application itself.
I’d be happy about feedback.