Wolfgang has posted 1 posts at DZone. View Full User Profile

Modularizing existing web applications with OSGi

03.30.2009
| 40130 views |
  • submit to reddit

 

OSGi modularization on the server side is well embraced by vendors (Spring DM Server et. al.). OSGi provides "true modules". With OSGi, we can keep together everything that belongs together. For web applications, that means we can deploy code, resources, JSPs etc all together in a bundle.

Today you can write a new web application and just run it with Equinox/Jetty or Felix http services. However, what if you need to keep evolving an existing web application and can't just stop and spend months rewriting everything to OSGi architecture? This article explores how you can build on an existing webapp and adopt OSGi for web components without first having to rewrite the whole application. An example is provided using Tomcat 6 and Equinox. Unlike SpringSource, IBM, Oracle and JBoss are not exposing OSGi to developers. However, the approach shown in the article might be made to work with their application servers, too.

The author of this article 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.

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 [footnote 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_repository\tools\launcher\dropins 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-INF/lib folder. Inspect web.xml. A servlet named equinoxbridgeservlet proxies requests with the URL pattern /rsp/* to the OSGi bundles. [3]

Step 2: Review /WebContent/home.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 a moment. 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: Review the content of folder /WebContent/module1 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 /WebContent/home.jsp do NOT link to the "classic" module1 that you just reviewed, but already to its "osgified" or "bundelized" version. I kept the classic modules in the project for your comparison. You can make them 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-INF/tld. 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 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.

Because we have the ability to simply add new features and capabilities to the environment, we are no longer forced to think in application-centric terms. OSGi on the server offers us amazing flexibility to adapt, shrink, grow, evolve, and morph an application over time.

If all functionalities of the web application are osgified, you can also run the web application in a "pure" OSGi environment that 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, iBatis, 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.

On the Arguments Tab of MyPureOSGiWebConfiguration, add -Dorg.osgi.service.http.port=8082 -Dorg.rsp.webapp.contextroot=MyPureOSGiWebProject to VM arguments. Click Run.

3. 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!".

Published at DZone with permission of its author, Wolfgang Gehner.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)