Java Development News:

Modularizing Existing Web Applications With OSGi: A Migration Path to OSGi

By Wolfgang Gehner

01 Apr 2009 | TheServerSide.com

Introduction

This article takes you through 12 easy steps to understand how OSGi bundles can be used within an existing classic WAR application. Prior experience with Eclipse Web Tools (Tomcat runtime and launcher) is recommended. The code referred to in this article is available for download here [1].

Run the example

Here is what you do to setup your workspace and run the code:

1. Make sure you have Eclipse Ganymede (JEE version) and a Tomcat 6 server runtime configured. Download and add the projects in rsp4j-example-workspace.zip to your workspace. Set the target platform in Preferences-Plug-in Development to [yourworkspace]rsp_repository rsp_target_platform. In Preferences-Java-Installed JREs-Execution Environments, map JavaSE-1.6 to your 1.6 JRE or JDK.

2. Add Tomcat to the Servers view and add MyClassicWarProject to it. Copy org.eclipse.pde.http.ui_1.0.0.jar from rsp_repositorytoolslauncherdropins to the dropins folder of Eclipse then restart the workbench. From the green "Run Configurations..." button, create a new "Equinox Http" configuration, name it MyClassicWarConfiguration, and on the Bundles tab select all bundles in Workspace except com.mycompany.myapp.root and in Target Environment except javax.servlet. For org.rsp.example.resources.css (2.0.0) set Auto-Start to false. Click Apply and Run. [2]

3. In a web browser, go to http://localhost:8080/MyClassicWarProject/home.jsp. You should see the welcome page of the rsp4j-demo webapp shown below:


[Image 1: Browser output]

Twelve easy steps to learn the example

Step 1: In MyClassicWarProject, inspect the Web App Libraries. The jars org.eclipe.equinox.servletbridge.jar, rsp-pdeframeworklauncher-1.0.0.jar and servletbridge.jar were added to the WEB-INFlib folder. Inspect web.xml. A servlet named equinoxbridgeservlet proxies requests with the URL pattern /rsp/* to the OSGi bundles. [3]

Step 2: Review WebContenthome.jsp. This page contains JSTL imports such as to /rsp/module1/module1.jsp. The response is handled by the plugin project com.mycompany.module1 that we will take a closer look at in step 4. Here we are integrating classic WAR and new OSGi bundle content. [4] Of course you can also access bundle content directly from the browser, as with http://localhost:8080/MyClassicWarProject/rsp/module1/module1.jsp.

Step 3: Let's look at what the project would look like without OSGi first. Review the content of folder WebContentmodule1 and the source code under com.mycompany.module1 of MyClassicWarProject. Your projects may have a similar structure. Classes and web resources are mixed up with other classes and web resources. In a classic WAR, you can isolate the module's classes in their own JARs but you can not keep the module's web resources in the JAR. OSGi bundles give you a convenient way to keep things together that belong together. In other words, true modules!


[Image 2: Classic WAR content]

The c:import tags in WebContenthome.jsp of course do NOT link to the "classic" module1 that you just reviewed. The classic module has been decommissioned, I just kept it in the project for your comparison. The c:import tags link to the new, "osgified" or "bundelized" version under /rsp. However, if you wish, you can make the classic module re-appear on /home.jsp by setting the JSTL variable "rsp" in home.jsp from "/rsp" to "" (empty string). We will look at the osgified version next.

Step 4: Expand the project com.mycompany.module1 in the workbench. It looks very similar to a WAR project, but it is an OSGi bundle (Plug-in in Eclipse terms). It was created with menu File-New-Project..-Plug-in Development-Plug-in Project with the default settings in the wizard (OSGi-Framework: Equinox). It has an OSGi-specific MANIFEST.MF in the folder META-INF.


[Image 3: OSGi bundle project com.mycompany.module1]

Step 5: Open MANIFEST.MF. On the Overview tab, you can see that the class com.mycompany.module1.Activator is specified as Activator. OSGi invokes it when the bundle starts, and bundle's web resources are configured from there, much like what the web container does when reading from web.xml on application startup. We will look at the Activator class in a moment. JavaSE-1.6 is specified as Execution Environment.


[Image 4: MANIFEST.MF Overview tab]

Step 6: On the Dependencies tab of MANIFEST.MF you see the list of Imported Packages this bundle needs. OSGi will do the magic to find and load the classes in those packages from other bundles in the application [5]. Under the heading Automate Management of Dependencies, I needed to specify javax.servlet to avoid build errors in the project.


[Image 5: MANIFEST.MF Dependencies tab]

Step 7: On the Build tab of MANIFEST.MF, under Binary Build the folders META-INF and WebContent are checked. The MANIFEST.MF tab shows the original text of the manifest.

Step 8: Expand the folder WebContent. For automatic JSP compilation to work as in a WAR, I needed to include the TLDs in the folder WEB-INFtld. The bundle also has a web.xml but this is needed only so that the Jasper compiler does not choke. Bundle configuration is done from the Activator.

Since code in OSGi bundles is isolated from code in the WAR project and Tomcat's own Jasper cannot access it, JSPs in bundles have their own bundelized Jasper compiler.

Step 9: Expand the folder src. You will find the class com.mycompany.module1.Activator. The other classes are the same as their non-osgified versions.

Open the class Activator. It extends HttpActivator, which is loaded from the bundle org.rsp.http. I wrote the HttpActivator to simplify initializing the web resources. It contains an instance of WebXml which is a Java descriptor I created to cover the most important features you find in a classic web.xml: Servlets, Filters, Context Parameters and Welcome-Files; Listeners forthcoming. [6]


[Image 6: Bundle Activator class]

HttpActivator uses the context parameter BUNDLE_URI_NAMESPACE to make JSP's, servlets and other resources available to the WAR and the browser under /rsp/module1. The method call .addServlet is selfexplanatory.

MyServlet can be accessed from outside the bundle with /rsp/module1/myservlet. However, the proxy servlet for /rsp/* is smart and rewrites the context paths so that a JSP inside the bundle can call the same servlet simply with <c:import url="/myservlet"/>

Step 10: Just as HttpActivator maps all JSP's under WebContent to /module1, HttpActivator also maps all static resources under WebContent to /module1. You can try this with

http://localhost:8080/MyClassicWarProject/rsp/module1/static.html

Step 11: Just for fun, let's play with OSGi versioning. The Tomcat console is also a console for the OSGi environment. Type "ss" into the console and hit <Enter>, and you should see a list of the bundles that are part of this application.


[Image 7: Tomcat/OSGi console]

You will find entries like

63 ACTIVE org.rsp.example.resource.css_1.0.0 85 RESOLVED org.rsp.example.resource.css_2.0.0

The state for org.rsp.example.resource.css (2.0.0) is RESOLVED because we set "Auto-Start" to false in the launch configuration. Typing stop 63 <Enter> would stop the first bundle, typing start 85 <Enter> would start the second. If you do this and refresh http://localhost:8080/MyClassicWarProject/home.jsp in the browser you will see how the style has changed from Arial to Times by replacing the bundle with a new version. OSGi of course lets you uninstall any bundle and install new ones (e.g. with install file:mypath_to_bundle.jar) from scratch so you do not have to restart Tomcat or modify your launch configuration.

Step 12: For some more fun, let's play with OSGi packaging. On /home.jsp, click on the link "go inside (org.rsp.example.http.jsp2)" that brings you to a JSP test page. Hit the browser back button return to /home.jsp. Let's say you have a client whom you don't want to have this JSP test page module. You would simply not include that bundle in your distribution [7], and the item would not show on /home.jsp. You can simulate this by finding the bundle ID for org.rsp.example.http.jsp2_1.1.0 in the console and type uninstall [ID]. Refresh the browser and see how the menu item has disappeared.

Instead of using the command line, you could install, uninstall, start and stop bundles with an OSGi console such as KnopflerFish.

If all functionalities of the web application are osgified, you can also run the web application in a "pure" OSGi environment has bundles that provide the HTTP service, such as a bundelized Jetty. [8]

Summary

It is possible to build on an existing webapp and adopt OSGi for web components without having to rewrite the whole application. With the integration approach shown, classic WAR content and new bundles can coexist and be connected in the same web application. You might choose to migrate existing functionality to bundles over time (while always keeping the WAR operational) or simply add all new functionality in bundles. You could use or recombine those bundles in other web projects, too.

In short, you have the best of both worlds: you can continue to run your application in the trusted web container and modularize/componentize your functionality for pluggability and easy reuse.

The example only shows integration of JSPs but I have also successfully integrated Struts1+Tiles, Struts2, Tiles2, Wicket, Hibernate and Logging, either as prototype or in a commercial development. For some situations the Equinox code had to be patched, and other tricks applied (example: how to avoid "bleeding" of a Struts2 value stack in the WAR to a Struts2 instance in bundles). Let me know if you think you could use help kickstarting your particular project. If you find the bundle org.rsp.http helpful, your company might wish to sponsor its open source evolution. I can be reached at wgehner at gmail dot com.

Footnotes:

[1] Download rsp4j-example-workspace.zip at https://sourceforge.net/project/showfiles.php?group_id=255822, or get it from SVN: https://rsp4j.svn.sourceforge.net/svnroot/rsp4j/trunk

[2] If you need to re-run the application after you have stopped Tomcat on the Servers view or Console, click the green Run button again. If you just restart Tomcat on the Servers view, the OSGi bundles will not get refreshed.

[3] One reason why we don't map all requests to the new OSGi bundle (as with "/*") is that bundles use their own JSP compiler, and such a mapping would make JSPs in the classic WAR inaccessible. Another reason is that it is easier to see what is served by the new bundles.

[4] I found that the JSP compiler chokes if you do a c:import from the War to a JSP in a bundle if that JSP does a further c:import to a JSP (not servlet) in the bundle. If that concerns you, you can use AJAX to wrap the request to the JSP in a bundle. It is not a problem to do a c:import from one bundle to another (see com.mycompany.myapp.root and footnote [8]).

[5] To keep the architecture as open as possible, I have tried to avoid using "Required Plug-ins" and have not used any Eclipse-specific extension points.

[6] Instead of
webXml.addServlet(MyServlet.class).addMapping("/myservlet");
it might be nice to be able to use XML configuration for bundles instead, as in:

<servlet> <servlet-name>myservlet</servlet-name> <servlet-class>com.mycompany.module1.MyServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>myservlet</servlet-name> <url-pattern>/myservlet</url-pattern> </servlet-mapping>

There is ongoing related work by Jochen Hiller on a web.xml extension point for this (see http://www.eclipsecon.org/2008/sub/attachments/Modular_web_applications_based_on_OSGi.pdf).

[7] Distribution of the WAR including bundles into production is not covered by this article. In short, you can deploy the bundles in OSGi JAR format with the WAR or link to their physical location from the WAR.

[8] How to run the application without a servlet container:

  1. Set the target platform in Preferences-Plug-in Development to [yourworkspace]rsp_repository rsp_target_platform_jetty. From the green "Run Configurations..." button, create a new "OSGi" configuration and name it "MyPureOSGiWebConfiguration".
  2. On the Bundles tab of MyPureOSGiWebConfiguration, select all bundles in Workspace including com.mycompany.myapp.root and in Target Environment including javax.servlet, org.eclipse.equinox.http.jetty and org.mortbay.jetty. For org.rsp.example.resources.css (2.0.0) set Auto-Start to false. Click Apply.
  3. On the Arguments Tab of MyPureOSGiWebConfiguration, add
    -Dorg.osgi.service.http.port=8082
    -Dorg.rsp.webapp.contextroot=MyPureOSGiWebProject
    to VM arguments. Click Run.
  4. In a web browser, go to http://localhost:8082/MyPureOSGiWebProject/. You should see the welcome page of the rsp4j-demo webapp including the slogan "Look Ma, no WAR!".

Biography

Wolfgang Gehner is co-author of the book "Struts Best Practices" published in German and French. He has been working with Java n-tier web since 1998 and has previously published on the subject of server-side OSGi. He has spent the last 2.5 years architecting and coding OSGi-componentized web applications. Through his company he provides consulting services, mentoring and training (English, French, German) on Best Practices in web technologies and is available for mandates including architecture and development. Wolfgang can be reached at wgehner at gmail dot com.

This article was reviewed by Kirk Knoernschild and John Wilson.