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_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 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 \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 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-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 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:
- 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".
- 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.
-
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.
from http://www.theserverside.com/tt/articles/article.tss?l=MigratingPathToOSGi