OSGi, Maven, Pax, and web applications

This post is written for developers that are trying to deploy a .war file inside of an OSGi runtime environment.  I experienced a lot of pain along the way to making my solution work, so hopefully this will alleviate any pain that you might have.

The Scenario

This post primarily reflects the environment in which I am writing and deploying my application.  Here are the key points that led me up to my course of action:

  • I use Maven to manage my project.
  • I already had an existing web application .war that had no ‘knowledge’ of OSGi.
  • I knew I would need to ‘OSGi-ify’ the .war and turn it into a bundle, since this is required by the OSGi 4.2 Enterprise specification
  • Once I had the new OSGi-compatible .war, I would need to quickly and easily start it up in an OSGi environment to test it.

The Setup

Based on my above environment/needs, I decided on the following:

  • Because I use maven and I have created plain (non-web) OSGi bundles with the Maven Bundle Plugin in the past, it would make sense for me to use it again to make my .war into a bundle.
  • Again, because I use maven, I want to launch my ‘OSGi-ified’ .war in an OSGi environment directly from maven.  I didn’t want to do anything painful like manually copy and install the .war into an OSGi container installed elsewhere.  That would be a pain that would frustrate me and infringe on my productivity.The Maven Pax Plugin looked like it could easily start an OSGi runtime (Felix, Equinox, etc)  as well as provide the additional web support that is required to launch .war files.  So, I decided to give that a go.

The Solution

Step 1: Make your .war OSGi-compatible

This was actually pretty easy, but there were two major problems I encountered along the way that caused me hours of suffering.  May my pain be your salvation! :)

Using the maven-bundle-plugin, you essentially tell it to create a MANIFEST.MF file with OSGi entries, and then you tell the maven-war-plugin to use that generated file.  All it takes is adding the following two plugins to your war project’s <plugins> section:

<plugin>
  <artifactId>maven-war-plugin</artifactId>
  <configuration>
    <archive>
      <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
    </archive>
  </configuration>
</plugin>
<!--
    Enable support for non-bundle packaging types
    See: http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html
-->
<plugin>
  <groupId>org.apache.felix</groupId>
  <artifactId>maven-bundle-plugin</artifactId>
  <extensions>true</extensions>
  <executions>
    <execution>
      <id>bundle-manifest</id>
      <phase>process-classes</phase>
      <goals>
        <goal>manifest</goal>
      </goals>
      <configuration>
        <instructions>
          <Bundle-SymbolicName>CHANGEME</Bundle-SymbolicName>
          <Export-Package/>
          <Import-Package>javax.servlet,javax.servlet.http,javax.servlet.*,javax.servlet.jsp.*,javax.servlet.jsp.jstl.*</Import-Package>
          <DynamicImport-Package>javax.*, org.xml.sax, org.xml.sax.*, org.w3c.*</DynamicImport-Package>
          <Bundle-ClassPath>.,WEB-INF/classes</Bundle-ClassPath>
          <Embed-Directory>WEB-INF/lib</Embed-Directory>
          <Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
          <Embed-Transitive>true</Embed-Transitive>
          <Web-ContextPath>CHANGEME</Web-ContextPath>
          <Webapp-Context>CHANGEME</Webapp-Context>
        </instructions>
      </configuration>
    </execution>
  </executions>
  <configuration>
    <supportedProjectTypes>
      <supportedProjectType>jar</supportedProjectType>
      <supportedProjectType>bundle</supportedProjectType>
      <supportedProjectType>war</supportedProjectType>
    </supportedProjectTypes>
    <instructions>
      <!-- ... etc ... -->
    </instructions>
  </configuration>
</plugin>

Just remember to set the values appropriate to your application, namely those for the <Bundle-SymbolicName> and <Web-ContextPath> and <Webapp-Context> entries (we’ll discuss the latter two shortly).

After you’ve added the plugins and changed the appropriate values, a normal maven build (e.g. mvn package or mvn install) will produce your OSGi-compatible .war!

But what you see is deceptively simple – there are two things that prevented me from deploying successfully for hours.  Make sure you avoid them:

Manifest Entries: <Web-ContextPath> or <Webapp-Context>?

Notice the two following lines in the maven-bundle-plugin‘s <instructions> element:

<Web-ContextPath>CHANGEME</Web-ContextPath>
<Webapp-Context>CHANGEME</Webapp-Context>

Doesn’t that look annoyingly repetitive to you?  Why do we need two?

Well, let’s cover what they are.  As you might have guessed by looking at either one individually, they specify the context path under which your web app will be accessible, for example http://localhost:8080/myContextPath.  If you don’t specify them, most OSGi web container implementations will default your context path to your .war’s Bundle-SymbolicName,  For example, servicing your webapp from http://localhost:8080/com.company.my.bundle.symbolicName.  That’s just plain ugly, so you really want to specify them to ensure your urls look sane.  But why are there two entries?

When I originally added the manifest entries, the only one I specified was the <Web-ContextPath> entry.  This is after all the correct Manifest entry as mandated by the OSGi 4.2 Web Applications Specification (Chapter 128 in the OSGi Compendium document).  I read the specification and added that entry accordingly.  All should work fine then, right?

Well, it would have if the Pax Web support used by the maven-pax-plugin used to launch the .war (covered below), was 100% OSGi 4.2 compliant.  It’s not.  I sat at my desk for hours, running various tests until 3:30 in the morning, swearing at myself that I couldn’t believe I was screwing things up, when all the documentation said it should be right!

Much to my early-morning chagrin, I discovered that the existing Pax Web support (0.7.2 at the time of this writing), does not yet understand the OSGi standard <Web-ContextPath> entry.  There is currently an outstanding Jira issue for them to fix this.  Until that is fixed, we’ll need to add both: the OSGi standard one to ensure it works in your standard OSGi deployment environment and the Pax Web-specific <Webapp-Context> one to make Pax happy while you’re testing with Maven.  When that Jira issue is fixed, you can remove it and keep the standard one.

But what if you don’t want to launch your .war using the maven-pax-plugin?  You can remove the non-standard entry, right?  

Well, not so fast – there are OSGi container distributions, such as Apache Karaf (which is the Felix OSGi runtime + a lot of nice enterprise features), that use the same exact Pax Web mechanism as their implementation to support .war deployments.  That means if you deploy your .war to Apache Karaf as your production environment, because Karaf uses the same Pax Web mechanisms, the non-standard <Webapp-Context> entry must still be specified.

Its safest to keep both entries and have them both reference a maven property that you can set once to avoid repeating yourself:

<properties>
    <web.contextPath>foo</web.contextPath>
    ...
</properties>

...
    <instructions>
        ...
        <Web-ContextPath>${web.contextPath}</Web-ContextPath> 
        <Webapp-Context>${web.contextPath}</Webapp-Context>
    </instructions>
...

web.xml: use a <welcome-file-list>

Now that we got the Manifest entries out of the way, there’s one final issue that caused me grief: welcome files.

My web app has an index.jsp file at the root of the war file.  It is incredibly simple – all it does is redirect the user to a nice ‘home’ landing page:

<%@ page session="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%-- Redirect to the home page --%>
<c:redirect url="/home/"/>

Trivial, right?

Well, my web.xml in my pre-OSGi .war did NOT have a <welcome-file-list> entry in it.  Tomcat and Jetty don’t care about this – they have a list of sensible defaults.  Because of that, I haven’t specified a welcome-file-list in years – I just didn’t think about it.

However, the  the OSGi war deployer mechanism I was using (Pax Web’s war extender) does require it.  It was not able to handle a request coming in to http://localhost:8080/myapp.  It didn’t know to automatically use the http://localhost:8080/myapp/index.jsp file.

This was equally as frustrating as the non-standard Manifest entry discussed above and further contributed to my hours-long “shoot me now, I hate OSGi” session – there were no error messages explaining why the request failed (terribly frustrating).  But I eventually figured it out by going through the arduous process of setting up an entirely separate ‘clean room’ .war project and trying different things from scratch.

I added the following XML snippet at the bottom of my web.xml file, and all was well again:

<web-app ...>
    ...
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

Step 2: Launch your OSGi .war from Maven

Now that you have an OSGi-compatible .war, ideally you want to be able to launch it and test it immediately.  It would be a major pain to have to build your .war and manually copy it or install it into your OSGi deployment environment (e.g. Apache Karaf, Eclipse Virgo, Apache Geronimo, etc…).  We want instant gratification with no setup!!!

It’s the maven-pax-plugin to the rescue! (with the exception of the manifest entry and welcome-file-list complaints above)

Once you’ve completed both parts of step 1 above, the rest is smooth sailing.  Add the following plugin to your .war project’s <plugins> section:

<plugin>
    <groupId>org.ops4j</groupId>
    <artifactId>maven-pax-plugin</artifactId>
    <configuration>
        <provision>
            <param>--platform=felix</param>
            <param>--profiles=compendium,web,war</param>
        </provision>
    </configuration>
</plugin>

Notice that the first <param>, --platform=felix,  reflects my personal test environment.  You could specify --platform=equinox to use the Eclipse Equinox OSGi framework instead.  You could also specify other platforms as well.  See the maven-pax-plugin and Pax Runner documentation for more options.  You’ll naturally choose settings that will best reflect your production runtime environment.

The second <param> is what is important for testing .wars.  It specifies Pax Runner profiles that enable .wars to be deployed.  You can add other profiles as necessary, but those three are required for deploying .wars.

To run your web app, just execute the following:

mvn install pax:run

Super easy!

Success!

Once the two issues above were taken care of, and I could launch my new OSGi .war, it was accessible via http://localhost:8080/myapp and everything worked as expected.

But I hope this helps you, weary-eyed OSGi blog-searcher.  Good luck!

8 thoughts on “OSGi, Maven, Pax, and web applications”

  1. Have you used a exisiting webproject to generate the war or the war as dependency ? The question why i ask is the following. I fetch a existing war over maven and like to generate a manifest for the war.

  2. @xonix – no, sorry, I haven’t tried to use an existing war with this approach. I’ve always used in in the war module directly. But the bundle plugin supports creating manifests afterwards as well. You’d probably need to use the dependency plugin and explode the war and then use the bundle plugin to create the new manifest and then use the war plugin to re-zip up the exploded directory. I’m not sure if this is the most efficient approach though!

  3. Nice detailed description, I wonder if you would still have the same issues with the current or the upcoming version of Pax-Web and what could be still improved.

  4. Thank you kind sir… I was in complete pain in OSGI Compendium + WAR hell until I stumbled on your article. Super helpful !!!!

  5. I’m wondering if I convert a war to OSGi bundle running in an OSGi container, such as Karaf. Can a class inside of the war can access any class outside of the war within the OSGi container? Such as a web service (a Servlet) in the war can easily access camel routes outside of the war within the OSGi container?

    Thanks in advance.

    Ming

  6. Thank you for the detailed explanation. I followed your article and I couldn’t access the webapp in CQ5 instance. The bundle activated and started properly without any issues. But i am unable to access it. I installed it manually in the felix console of CQ5. Any reason why it wouldn’t work?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>