Running Spring MVC Web Applications in OSGi
For the past couple of weeks, I've been developing a web application that deploys into an OSGi container (Equinox) and uses Spring DM's Spring MVC support. The first thing I discovered was that Spring MVC's annotations weren't supported in the M1 release. This was apparently caused by a bug in Spring 2.5.3 and not Spring DM. Since Spring DM 1.1.0 M2 was released with Spring 2.5.4 today, I believe this is fixed now.
The story below is about my experience getting a Spring MVC application up and running in Equinox 3.2.2, Jetty 6.1.9 and Spring DM 1.1.0 M2 SNAPSHOT (from last week). If you want to read more about why Spring MVC + OSGi is cool, see Costin Leau's Web Applications and OSGi article.
To get a simple "Hello World" Spring MVC application working in OSGi is pretty easy. The hard part is setting up a container with all the Spring and Jetty bundles installed and started. I imagine SSAP might solve this. Luckily for me, this was done by another member of my team.
After you've done this, it's simply a matter of creating a MANIFEST.MF for your WAR that contains the proper information for OSGi to recognize. Below is the one that I used when I first tried to get my application working.
Manifest-Version: 1 Bundle-ManifestVersion: 2 Spring-DM-Version: 1.1.0-m2-SNAPSHOT Spring-Version: 2.5.2 Bundle-Name: Simple OSGi War Bundle-SymbolicName: myapp Bundle-Classpath: .,WEB-INF/classes,WEB-INF/lib/freemarker-2.3.12.jar, WEB-INF/lib/sitemesh-2.3.jar,WEB-INF/lib/urlrewritefilter-3.0.4.jar, WEB-INF/lib/spring-beans-2.5.2.jar,WEB-INF/lib/spring-context-2.5.2.jar, WEB-INF/lib/spring-context-support-2.5.2.jar,WEB-INF/lib/spring-core-2.5.2.jar, WEB-INF/lib/spring-web-2.5.2.jar,WEB-INF/lib/spring-webmvc-2.5.2.jar Import-Package: javax.servlet,javax.servlet.http,javax.servlet.resources,javax.swing.tree, javax.naming,org.w3c.dom,org.apache.commons.logging,javax.xml.parsers;resolution:=optional, org.xml.sax;resolution:=optional,org.xml.sax.helpers;resolution:=optional
Ideally, you could generate this MANIFEST.MF using the maven-bundle-plugin. However, it doesn't support WARs in its 1.4.0 release.
You can see this is an application that uses Spring MVC, FreeMarker, SiteMesh and the URLRewriteFilter. You should be able to download it, unzip it, run "mvn package" and install it into Equinox using "install file://<path to war>".
That's all fine and dandy, but doesn't give you any benefits of OSGi. This setup works great until you try to import OSGi services using a context file with an <osgi:reference> element. After adding such a reference, it's likely you'll get the following error:
SEVERE: Context initialization failed org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/osgi]
To fix this, add the following to your web.xml (if you're using ContextLoaderListener, as an <init-parameter> on DispatcherServlet if you're not):
<context-param> <param-name>contextClass</param-name> <param-value>org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext</param-value> </context-param>
After doing this, you might get the following error on startup:
SEVERE: Context initialization failed org.springframework.context.ApplicationContextException: Custom context class [org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext] is not of type [org.springframework.web.context.ConfigurableWebApplicationContext]
To fix this, I change from referencing the Spring JARs in WEB-INF/lib to importing the packages for Spring (which were already installed in my Equinox container).
Bundle-Classpath: .,WEB-INF/classes,WEB-INF/lib/freemarker-2.3.12.jar, WEB-INF/lib/sitemesh-2.3.jar,WEB-INF/lib/urlrewritefilter-3.0.4.jar Import-Package: javax.servlet,javax.servlet.http,javax.servlet.resources,javax.swing.tree, javax.naming,org.w3c.dom,org.apache.commons.logging,javax.xml.parsers;resolution:=optional, org.xml.sax;resolution:=optional,org.xml.sax.helpers;resolution:=optional, org.springframework.osgi.web.context.support, org.springframework.context.support, org.springframework.web.context, org.springframework.web.context.support, org.springframework.web.servlet, org.springframework.web.servlet.mvc, org.springframework.web.servlet.mvc.support, org.springframework.web.servlet.view, org.springframework.ui, org.springframework.web.servlet.view.freemarker
After rebuilding my WAR and reloading the bundle in Equinox, I was confronted with the following error message:
SEVERE: Context initialization failed org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'freemarkerConfig' defined in ServletContext resource [/WEB-INF/myapp-servlet.xml]: Instantiation of bean failed; nested exception is java.lang.NoClassDefFoundError: freemarker/cache/TemplateLoader at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:851)
As far as I can tell, this is because the version of Spring MVC installed in Equinox cannot resolve the FreeMarker JAR in my WEB-INF/lib directory.
To prove I wasn't going insane, I commented out my "freemarkerConfig" and "viewResolver" beans in myapp-servlet.xml and changed to a regular ol' InternalResourceViewResolver:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"/> <property name="suffix" value=".jsp"/> </bean>
This worked and I was able to successfully see "Hello World" from a JSP in my browser. FreeMarker/SiteMesh worked too, but FreeMarker didn't work as a View for Spring MVC.
To attempt to solve this, I create a bundle for FreeMarker using "java -jar bnd-0.0.249.jar wrap freemarker-2.3.12.jar" and installed it in Equinox. I then change my MANIFEST.MF to use FreeMarker imports instead of referencing the JAR in WEB-INF/lib.
Bundle-Classpath: .,WEB-INF/classes,WEB-INF/lib/sitemesh-2.3.jar,WEB-INF/lib/urlrewritefilter-3.0.4.jar Import-Package: javax.servlet,javax.servlet.http,javax.servlet.resources,javax.swing.tree, javax.naming,org.w3c.dom,org.apache.commons.logging,javax.xml.parsers;resolution:=optional, org.xml.sax;resolution:=optional,org.xml.sax.helpers;resolution:=optional, org.springframework.osgi.web.context.support, org.springframework.context.support, org.springframework.web.context, org.springframework.web.context.support, org.springframework.web.servlet, org.springframework.web.servlet.mvc, org.springframework.web.servlet.mvc.support, org.springframework.web.servlet.view, org.springframework.ui, org.springframework.web.servlet.view.freemarker, freemarker.cache,freemarker.core,freemarker.template,freemarker.ext.servlet
Unfortunately, this still doesn't work and I still haven't been able to get FreeMarker to work with Spring MVC in OSGi. The crazy thing is I actually solved this at one point a week ago. Shortly after, I rebuilt Equinox from scratch and I'm been banging my head against the wall over this issue ever since. Last week, I entered an issue in Spring's JIRA, but thought I'd fixed it a few hours later.
I've uploaded the final project that's not working to the following URL:
If you'd like to see this project work with Spring MVC + JSP, simply modify myapp-servlet.xml to remove the FreeMarker references and use the InternalResourceViewResolver instead.
I hope Spring DM + Spring MVC supports more than just JSP as a view technology. I hope I can't get FreeMarker working because of some oversight on my part. If you have a Spring DM + Spring MVC application working with Velocity or FreeMarker, I'd love to hear about it.
Great to see you working on OSGi stuff.
With regard to your Freemarker problem, sadly bringing a third party library into OSGi is rarely as simple as just running "bnd wrap" over it. The issue is the dependencies... bnd does bytecode analysis to discover all of the static dependencies inside Freemarker and it adds those to the manifest. So if it discovers a dependency on (say) org.apache.commons.lang, then the FM bundle won't resolve unless you have commons-lang bundleized and installed as well.
The real the problem is that many libraries include dependencies on crazy stuff. For example lots have a dependency on JUnit because they have not properly separated their tests from their runtime parts. However bnd has no way to automatically discriminate between "true" runtime dependencies and the ones that are just sitting around in the JAR. So you have to help it along. The initial step is to run bnd and then look at the imports it has included, then you determine which of those imports are not really needed, and you write a properties file to tell bnd that those imports should be made optional.
I am in the process of documenting these issues in my book, but I haven't yet released the chapter on working with 3rd party libraries. But you may still find something useful in the chapters that I have released: http://neilbartlett.name/blog/osgibook
Neil PS I tried to download Freemarker myself and check if this was in fact the problem, but unfortunately SourceForge seems to be down at present.
Posted by Neil Bartlett on April 30, 2008 at 05:49 AM MDT #
Posted by Carlos Sanchez on April 30, 2008 at 10:03 AM MDT #
This was a great challenge application for the SpringSource Application Platform - especially in the light of the comments from Neil Bartlett and Carlos Sanchez above.
I'm pleased to say that getting this application to work on the SpringSource Application Platform was trivial, and a testament to the points I made in my blog last night Completing the picture, Spring, OSGi, and the SpringSource Application Platform.Here are the steps I followed:
I think this is a nice demonstration of the value proposition of the platform in smoothing the path of making enterprise libraries work under OSGi.
I'm happy to send you the updated code if you would like it, but it should be very easy to recreate these simplifications to your application :)Regards, Adrian.
Posted by Adrian Colyer on May 02, 2008 at 08:22 AM MDT #
Posted by Matt Raible on May 02, 2008 at 11:04 AM MDT #
If you want to use <osgi:reference> then you will need that context class, yes. We distinguish between "shared library" war files that simply use Import Package / Bundle / Library in their manifest, and "shared service" war files that also can inject references to OSGi services. These two stages form a migration path towards a true par file. Your initial application didn't use any "shared services" so the special context wasn't needed. Good luck with the application - do keep us posted on any issues you run into so that we can continue to make things work better for you.
Posted by Adrian Colyer on May 03, 2008 at 12:33 AM MDT #
Please bear in mind that using the Import-Library and Import-Bundle headers as recommended by Adrian will tie you to S2AP, as these are non-standard extensions introduced by SpringSource.
My recommendation is to stick with the full Import-Package list. Yes it is long, but at least the dependencies of your bundle are defined <strong>in your bundle</strong> rather than in some external artifact which might change.
I have detailed some of my issues with SpringSource's extensions in my blog post here: http://neilbartlett.name/blog/2008/05/01/springsource-app-platform-is-a-curates-egg/
Posted by Neil Bartlett on May 08, 2008 at 04:09 AM MDT #
Posted by Rob Harrop on May 08, 2008 at 11:49 AM MDT #
Posted by Matt Raible on May 14, 2008 at 10:38 AM MDT #
The tool that Adrian and Rob mentioned does not yet exist; however, I'll share a perhaps little know tip with you...
If you create a bundle using Import-Bundle or Import-Library, you can view the Platform's trace (i.e., PLATFORM_HOME/serviceability/trace/trace.log) and see exactly how those manifest headers get translated into standard OSGi Import-Package statements. Just search for "transformed from:", and just after that you'll find a "to:" with the transformed manifest immediately following.
Now to answer the question you posted on the S2AP beta forum regarding how to get your sample web application to run on the Platform without any SpringSource specific manifest headers: As Adrian already mentioned, it's rather trivial to convert your web-app to run using Import-Bundle and Import-Library. So, to get it to run on the SpringSource Application Platform using only Import-Package, simply follow Adrian's above steps, but use the following manifest instead:
Delete the following, as they are not necessary for deploying WARs or Web Modules on the S2AP:
And in web.xml, use PlatformOsgiBundleXmlWebApplicationContext instead of OsgiBundleXmlWebApplicationContext as the 'contextClass' context-param for Spring MVC's ContextLoaderListener. For example:
I hope this gets you up and running with your Freemarker web-app on the S2AP!
Posted by Sam Brannen on May 16, 2008 at 10:03 PM MDT #
Hi, I am trying to test and install:
but when I do:
and when I go to http://localhost:8080/myapp I get:
Posted by Misha Koshelev on October 10, 2010 at 09:36 PM MDT #
Posted by JIRA: OpenMRS Trunk on October 11, 2010 at 04:30 PM MDT #
I have installed successfully on Virgo Web Server:
I enable the Equinox telnet console by uncommenting the following line:
then drop the following bundle into pickup:
I go to the console, install bundle, it is active:
Yet get a 404 from the suggested URL:
Posted by Misha Koshelev on October 12, 2010 at 07:18 PM MDT #
Posted by Matt Raible on October 12, 2010 at 10:07 PM MDT #
I download your zip and packaged and installed the war. But I am getting this error.
Where I am wrong, Thanks.
Posted by Muthu on August 31, 2013 at 12:33 PM MDT #