This article is based on Enterprise OSGi in Action , to bepublished on March 2012. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code ‘java40beat’ and get 40% discount on eBooks and pBooks ]
also read:
- Java Tutorials
- Java EE Tutorials
- Design Patterns Tutorials
- Java File IO Tutorials
Introduction
In Java EE, the Servlet and Java Server Pages models have provided the basic building blocks for Java web applications for many years. OSGi Web Applications are a standardized OSGi version of JEE web applications. An OSGi web bundle is very similar to a JEE WAR, except that it also gets the benefits of operating in an OSGi framework. Enterprise OSGi web bundles are known as WABs. (In contrast to WARs, which are Web ARchives, WABs are Web Application Bundles.)
Building a simple OSGi web application bundle
Let’s give WABs a try. You can use your favorite OSGi or JEE development tools. All you really need for now is the ability to compile Java code and build jars.
What is WAB?
As mentioned above, WAB is just an OSGified WAR archive. Besides the ususal OSGi headers it must in addition contain a special header, Web-ContextPath, specifying the web application context path. Our WAB has (beside some other) the following headers present in the manifest
Web-ContextPath: helloworld Webapp-Context: helloworld Bundle-ClassPath: WEB-INF/classes
WAB layouts
The simplest WAB contains three files. These are a Java servlet class, a jar manifest, and a web deployment descriptor. Figure 1 shows how they’re laid out in the WAB. Like WARs, WABs are just a special kind of jar; unlike WARs, the Enterprise OSGi specification does not require a particular file extension. As a result WAB, files may have any extension but typically use .jar or .war.
Figure 1 The layout of the fancyfoods.web jar. All code lives in WEB-INF/classes. The web container looks WEB-INF/web.xml to find out what servlets are provided by the bundle. Finally, the standard jar manifest, META-INF/MANIFEST.MF includes important extra metadata for the OSGi container.
Web deployment descriptors
Let’s start with the deployment descriptor. Listing 1 shows the web.xml file for the web application. It’s a typical web.xml file whose syntax will be reassuringly familiar to everyone who has developed JEE web applications. The web application has one servlet, whose class is fancyfoods.web.SayHello.
Listing 1 The WEB-INF/web.xml file
<web-app> <servlet> <servlet-name>SayHello</servlet-name> #1 <servlet-class>fancyfoods.web.SayHello</servlet-class> </servlet><br /> <servlet-mapping> <servlet-name>SayHello</servlet-name> #2 <url-pattern>/SayHello</url-pattern> </servlet-mapping> </web-app> #1 A servlet with backing class SayHello #2 The URL is SayHello
A simple servlet
The servlet class SayHello is also exactly the same as it would be in a WAR. Listing 2 shows the source. There’s one method, which, unsurprisingly, issues a greeting to a user.
Listing 2 The SayHello.java file
package fancyfoods.web; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.*; public class SayHello extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter writer = response.getWriter(); #1 writer.append("Hello valued customer!"); } } #1 Write to the response's PrintWriter
So far, so familiar. It’s perhaps a bit anti-climactic that writing a WAB is so similar to writing a WAR in some respects, but this is one of the strengths of the enterprise OSGi programming model it’s like existing programming models, only better. The differences between WABs and WARs start to become obvious when we look at the manifest file.
A WAB manifest
The final file needed in your WAB is the bundle manifest. Every jar has a MANIFEST.MF file, but an OSGi bundle’s manifest has extra headers, such as the symbolic name of the bundle and the bundle’s version.
Listing 3 The MANIFEST.MF file
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: fancyfoods.web Bundle-Version: 1.0.0 Bundle-ClassPath: WEB-INF/classes Web-ContextPath: /fancyfoods.web Import-Package: javax.servlet.http;version="2.5", javax.servlet;version="2.5"
To be consistent with the layout of a WAR, the class files for fancyfoods.web have been packaged in the WEB-INF/classes folder. However, there’s actually no need for this. Classes can live anywhere in an OSGi bundle or even be spread across multiple locations. If the class files are not directly in the root directory, the classpath needs to be specified in the manifest:
Bundle-ClassPath: WEB-INF/classes
Packages used by the servlet that aren’t in the servlet’s own bundle must be explicitly imported in the manifest. Otherwise, they won’t be visible. The exception is that there is no need to import the core java language classes, java.*, which are implicitly imported. Bundle-wiring rules are a bit different for the java.* packages, which must come from the core Java Runtime for security and for compatibility with the Virtual Machine. Imagine if someone could replace the implementation of String or Integer!
In the case of the web bundle, this means the javax.servlet and javax.servlet.http packages are imported. The servlet is expected to work with any version of javax.servlet with version 2.5 or higher, up to but not including version 3.0.
Import-Package: javax.servlet.http;version="2.5", javax.servlet;version="[2.5, 3.0)"
WARNING What about Servlet 3.0?
The meaning of the version range “[2.5, 3.0)” isn’t entirely straightforward. You’re mostly right if you assume that 2.5 part implies version 2.5 of the servlet specification. However, 3.0 definitely does not mean version 3.0 of the servlet specification! Remember, OSGi versions are semantic package versions, not marketing or specification versions. Servlet 3.0 is backward compatible with servlet 2.5, and so the package versions for servlet 3.0 won’t be versioned at 3.0. Version 3.0 of the servlet packages would be some change to the servlet specification so radical that the interfaces would no longer be backward compatible. The reason the bottom range starts at 2.5 and not 1.0 is that, when the WAB specification was written, the current version was 2.5, and so 2.5 seemed like a logical starting point. Unfortunately, some application servers have deviated from the semantic version and use the package version 3.0 for the Servlet 3.0 specification, which doesn’t help!
The manifest includes one final header which is specific to WABs and defines the web context root. This header is required for a bundle to be recognized as a WAB. Many enterprise OSGi implementations also allow the context root to be changed after deployment.
Web-ContextPath: /fancyfoods.web
Build these three files into a jar, and the web application is ready to try out!
Deploying and testing
Because OSGi is so dynamic, testing OSGi bundles is pretty easy. The same bundle can be loaded repeatedly without having to stop or start anything else. If you’re as prone to typos as the authors, you’ll find this extremely handy.
The load directory
The Apache Aries runtime you assembled earlier provides a simple mechanism for discovering and starting new applications. The target directory includes a folder called load. Any OSGi bundles copied into this directory will automatically be started.
To try this out, start the Aries runtime with java -jar. Type osgi-3.5.0.v20090520.jar -console ss to see the list of installed bundles. Now copy the web bundle you’ve built into the load directory.
You’ll see a bunch of output scroll by in the OSGi console. Type ss again. You’ll see there’s one extra bundle listed, and it’s the fancyfoods.web bundle you just copied into the load directory. The fancyfoods.web bundle should be in ACTIVE state.
All that remains now is to try it out. Point a browser at http://localhost:8080/fancyfoods.web/SayHello. You’ll see more debug output scroll by in the OSGi console, and the browser should display a response like the one shown in figure 2.
A familiar feeling and important differences
Even with the extra headers in the manifest, the web bundle you’ve written looks a lot like a conventional JEE WAR. In fact, it’s so similar that you could probably deploy it as a WAR in a JEE application. So what’s different about it? A WAB is a bundle, and not just a normal jar, which means it has some new behaviors.
WAR-to-WAB conversion
The structure of WARs and WABs are similar enough that the enterprise OSGi specification supports automatic conversion of WARs to WABs at deploy time. A bundle symbolic name and package imports are automatically generated. This can be convenient when doing an initial migration from JEE to enterprise OSGi, but in general it’s better to write web applications as WABs. This ensures the bundle has a proper, well-known, symbolic name, and it also allows package imports to be versioned. Versioning package imports is always a good idea. The WAB format also provides a convenient mechanism for setting the web context root.
Package privacy
What are the implications of being an OSGi bundle? The biggest implication is actually what can’t be seen – nothing outside the fancyfoods.web bundle can see the SayHello class, because it’s not exported. This cozy privacy is exactly what you want because there’s no reason for any code outside your bundle (except for perhaps the web container, which can use the OSGi API) to be messing around directly with your servlet class. If you did want to make the SayHello class externally accessible for some reason, all that would be required is to add a package export of the fancyfoods.web package. However, you’d probably want to consider your design pretty carefully before doing this. Could the shared code be externalized to a utility bundle instead?
Figure 3 The class space of the fancyfoods.web bundle. It does not have any public packages. To confirm which bundle exports the javax.servlet bundle, type packages javax.servlet in your OSGi console or bundle 37 to see where all of the packages used by fancyfoods.web come from.
Although package privacy is a good thing, not all Java code can cope with it. Some existing libraries, particularly ones which use reflection to load classes, may not work properly.
Explicit dependencies
Being a bundle has a second implication, which is that all the packages required by SayHello must be explicitly imported in the manifest. If you’re first getting used to OSGi, this can seem like an unnecessary and annoying extra step.
Let’s step back and think about how Java handles package imports for classes. If you were writing a Java class, you’d always import the packages you were using, rather than expecting the compiler to choose a class with the right name from a random package. Some class names are pretty unique, and you’d probably end up with the right one, but other class names are not at all unique. Imagine how horrible it would be if your class could end up running against any class called Constants, for example.
Of course, you might also end up with the opposite problem—instead of a class name, which was too common, you could be trying to use a class that doesn’t exist at all. If the package you needed didn’t exist at all, you’d expect an error at compile-time. You certainly wouldn’t want the compilation to limp along, claim success, and produce a class which half worked.
Luckily, the Java compiler doesn’t do this. If your declared dependencies aren’t present, the compilation will fail quickly. At runtime, on the other hand, you’re in a situation that is pretty similar to the undeclared dependencies. You have to wait until your class is invoked to discover its dependencies are missing. You won’t ever end up running against a class from a totally different package to the one you expected, but you could end up running with a class of a different version, with a totally different methods and behaviors.
Explicitly declaring the dependency on the javax.servlet and javax.servlet.http packages ensures the fancyfoods.web bundle won’t be run in a container that doesn’t support servlets. Better yet, it won’t even be run on a container that supports an obsolete version of the servlet specification. To try this out, go to the OSGi console for the Aries runtime. At the prompt, use the packages command to see which bundles import and export the javax.servlet package:
osgi> packages javax.servlet
The response should be something like the following:
The output shows that the org.ops4j.pax.web.pax-web-jetty-bundle exports the javax.servlet package and four bundles import it, including fancyfoods.web.
What would happen if the Aries assembly hadn’t included Jetty? Quit the OSGi console and move the Jetty bundle (pax-web-jetty-bundle*jar) out of the target directory. (Don’t lose it though!) Restart the OSGi console and type ss to see the list of bundles. You’ll see a number of bundles, including fancyfoods.web, are in the INSTALLED state instead of the ACTIVE state. This means the bundles couldn’t be resolved or started because some dependencies were missing. Make a note of the bundle identifier to the left of fancyfoods.web in the bundle list. Try starting the bundle to see what happens:
Figure 5 If the Jetty bundle is removed from the runtime, the Fancy Foods web bundle cannot be started because no servlet implementation is present.
Because your assembly no longer has servlet support, fancyfoods.web won’t start. It definitely wouldn’t work properly if the code inside it was to run, so not starting is the right thing to do. Don’t forget to put the Jetty bundle back into the target directory before you try and use the web application again. (The authors forgot to do this on several occasions, and were very confused each time.)
Fragments
OSGi bundles have a third advantage over normal WARs. OSGi is all about modularity, and so OSGi bundles themselves can themselves have modular extensions, known as fragments. Fragments are extensions to bundles which attach to a host bundle and act in almost every way as if they were part of the host. They allow bundles to be customized depending on their environment. For example, translated resource files can be packaged up by themselves into a fragment and only shipped if needed. Fragments can also be used to add platform-specific code to a generic host.
Spicing things up with fragments
How would a fragment work with your little application? The first version of the fancyfoods.web application is only intended to work in English, but if the business takes off, it will expand into other markets. The first step in internationalizing fancyfoods.web is to externalize the strings in SayHello.java. Write a properties file with the following content:
SayHello.hello=Hello valued customer!
The new doGet method looks like:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter writer = response.getWriter(); Locale locale = request.getLocale(); #1 String bundleName = "fancyfoods.web.messages"; ResourceBundle resources = ResourceBundle.getBundle(bundleName, locale); #2 String greeting = resources.getString("SayHello.hello"); #3 writer.append(greeting); } 1 Where are we? 2 Get the right resource bundle 3 Get the translated message
Bundles, bundles, or bundles?
We’ve got a few different kinds of bundles floating around at the moment, but the resource bundles here are nothing to do with OSGi bundles—they’re just ordinary Java resource bundles.
If you build the bundle and test the web page, it will work exactly as it did before. This is reassuring if you’re English, but not ideal if you’re browsing in French. To try it out, change your web browser’s preferred language to French. (If you don’t want to do that, you can hardcode the locale in the getString() call in SayHello.java.) Most pages you browse to, like Google, for example, will show French text. However, if you reload the Fancy Foods web page, the greeting is disappointedly English. In order to get the Fancy Foods to display in French, you need to provide some French translations, obviously.
In order to be accessible to the SayHello class, the properties files need to be loaded by the same classloader, which (mostly) means they need to be in the same bundle. However, rebuilding jars is no fun, and you definitely don’t want to be repackaging your existing code every time you have a new translation. What you want to be able to do is be able to easily drop in support for other languages in the future.
Resource loading between bundles
We’ve simplified our discussion of resource loading slightly. It is in fact possible to load resources from other bundles, but it’s ugly. The package containing the resource must be exported by the providing bundle and imported by the consuming bundle. In order to avoid clashes with packages in the consuming bundle, the consuming bundle shouldn’t export the package it’s attempting to import. Having trouble following? You won’t be the only one! We’ve seen this pattern used, but we definitely don’t recommend it.
Luckily, this is the sort of job for which OSGi fragments are perfect. OSGi fragments are a bit like OSGi bundles. However, instead of having their own lifecycle and classloader, they attach to a host bundle. They share the host’s classloader and behave in almost every way as if they’re part of the parent.
However, they can be installed and uninstalled independently of the host.
Figure 6 OSGi fragments attach to a parent bundle and share its classloader.
In this case, a translation fragment can be built and attached to fancyfoods.web. To provide the translations, you’ll need a new fragment jar. All it needs inside it is a manifest and a properties file.
The French language properties file, messages_fr.properties, might read:
SayHello.hello=Bienvenue aux Aliments de Fantaisie!
The MANIFEST.MF is similar to a bundle manifest, but it has an extra header that identifies the host of the fragment—fancyfoods.web in this case. It is a good idea to also specify a minimum version of the host bundle, to ensure compatibility:
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: French language resources Bundle-SymbolicName: fancyfoods.web.nls.fr Bundle-Version: 1.0.0 Fragment-Host: fancyfoods.web;bundle-version="[1.0.0,2.0.0)" Bundle-ClassPath: .
Build the fragment into a jar, fancyfoods.web.nls.fr.jar. Once the fragment is built, you can drop it into the load directory of your running framework. Type ss and you’ll see your new fragment included in the list of bundles. Fragments can’t be started and stopped like bundles, so the fragment will be shown as INSTALLED.
Refresh the web bundle with refresh [web bundle number] and the fragment will attach to the bundle and move to the RESOLVED state.
Check the web page again, and the greeting should be shown in French. Delete the fancyfoods.web.nls.fr jar from the load directory, and try the web page again back to English!
Although internationalization is the most popular use for fragments, it’s not the only one. Anything can be put into a fragment, including Java classes. Including classes in fragments for plugability is not the best implementation. OSGi provides higher level ways of achieving plugability through services.
Summary
The web is one of the most fundamental parts of enterprise Java programming, providing a front-end for almost every enterprise application. In this article, we got you going with a simple web application.