This article accompanies the sample.osgi.httpservice sample on the WASdev GitHub. The sample illustrates how to use the Liberty SPI and a product extension, or ‘user feature’, to add HttpService support to OSGi Applications. See the Infocenter for more on developing features for Liberty. We assume that the reader has some familiarity with OSGi, the Liberty runtime, and Eclipse.
The Liberty runtime, user features and OSGi applications
The Liberty runtime, user features and OSGi applications are all comprised of OSGi bundles. These groups of bundles are separated from each as shown in the figure below. Bundles within each blue box can access each others’ packages and services in the usual manner. Restrictions apply to package and service accesses between boxes. The picture below shows packages and services labelled ‘API’ and ‘SPI’. We use the term API (Application Programming Interface) to mean a package or service visible to applications. An SPI (System Programming Interface) is a package or service visible to developers extending the Liberty platform, but not to applications that run on that platform.
User Features are packaged as standard OSGi subsystems: zip-formatted files with a .esa file suffix, containing a manifest and one or more bundles. The Infocenter lists the various headers that the manifest can contain. The IBM-API-Package, IBM-API-Service and IBM-SPI-Package headers are used to mark packages and services as API or SPI. There is no IBM-SPI-Service header, and so there are no SPI services. The “IBM-” prefix to these headers does not mean “reserved for use by IBM”: they are also for customers to use. “IBM-” simply means that it’s a non-standard header, defined by IBM.
- The kernel contains the core Liberty runtime, and the bundles required by IBM-provided features such as jndi-1.0 and blueprint-1.0. The kernel provides packages and services marked API to user features, OSGi applications, and the shared bundles space. It provides packages and services marked ‘SPI’ to User Features only.
- The bundles comprising user features run in the same Root Subsystem as kernel bundles. (This is an implementation detail, and refers to the subsystem support introduced in the OSGi R5 Enterprise specification.) However, only packages and services marked API or SPI can pass between the Kernel and User Feature boxes. User features export packages and services marked API to OSGi applications.
- The shared bundle space contains all the dependency bundles required by all the installed OSGi applications. (See the Infocenter here for more on dependency bundles and the provisioning process in general, from a WAS full profile perspective.) All the API packages and services required by every OSGi application or dependency bundle pass through the shared bundle space, and are available within it.
- Each OSGi application comprises one or more bundles. Each application’s bundles run in their own subsystem. They have no access to other OSGi applications. They can consume packages and services from the shared bundle space, including API packages and services from the kernel, and from user features.
The sample.osgi.httpservice GitHub sample
sample.osgi.httpservice is a GitHub project. It provides a number of Eclipse projects for use with the WebSphere Application Server Liberty Profile, and the WebSphere Developer Tools for Eclipse. The readme provides instructions on how to get it all working. The project contains a sample user feature, httpService-1.0, and two OSGi applications that use it.
The HttpService user feature
There are two main ways of presenting HTTP endpoints in OSGi. The first, which we have always supported in WebSphere OSGi applications is to use Web Application Bundles (WABs) as per the OSGi Enterprise specification. A WAB is an OSGi Bundle with a Web-ContextPath header in its manifest. The servlets that it contains are managed by the Java EE Web Container, and found either via web.xml or annotations. The second approach is to use the org.osgi.service.HttpService. This is an OSGi service for the programmatic registration of servlets. The first version of this sample does exactly that: the OSGi application creates an instance of a Servlet class, obtains the HttpService, and directly registers the Servlet class with it. The second sample shows an alternative approach, in which code in the user feature finds and registers servlets without the application having to do so itself. This is an example of the well-known Whiteboard Pattern.
Sample One: HttpService and redWeb
The most basic form of the sample comprises a simple user feature, ‘httpService-1.0′ and a single bundle OSGi application, ‘redWeb’. The user feature contains three bundles, two of which must be downloaded from the Internet:
- org.eclipse.osgi.services – This bundle provides the org.osgi.service.http.HttpService interface.
- org.eclipse.http.servlet – A basic HttpService implementation from the Eclipse Equinox project.
- httpServiceWab – This bundle exposes an org.eclipse.equinox.http.servlet.HttpServiceServlet with a context root of /httpService. OSGi Applications can obtain the HttpService that the HttpServiceServlet causes to be published, and register their own servlets underneath its /httpService context root.
The OSGi Application is very simple. redWeb uses Blueprint to track the HttpService, and inject it into a bean of class com.ibm.samples.osgi.httpservice.RedBean. RedBean creates a com.ibm.samples.osgi.httpservice.RedServlet and registers it with the HttpService with a ‘/red’ path, making the complete path host:9080/httpService/red. This example is extremely limited: for example, there is a single HttpService across the entire service, and only a single context root, /httpService which all applications must fit under. The sample also does not attempt to deal with lifecycle issues, such as unregistering the Servlet when the application stops.
Sample Two: greenWeb and the Whiteboard Pattern
The second, ‘green’ sample application uses the same user feature, but adds a second, single-bundle ‘greenWeb’ OSGi application. In this approach, the application uses Blueprint to instantiate a bean that extends Servlet, and publishes it as an OSGi service. Code in the user feature detects this service, and then registers it with the HttpService with a context root of /httpService/bundleName – in this case, /httpService/greenWeb. This isn’t very clever, since it means there’s a limit of one servlet per bundle, but it’s enough to illustrate the point. Here’s how the servlet is declared in greenWeb:
<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"> <bean id="greenServlet" class="com.ibm.sample.httpservice.green.GreenServlet"/> <service ref="greenServlet" interface="javax.servlet.Servlet"/> </blueprint>
All the work is done within com.ibm.sample.osgi.httpservice.wab.Activator within the httpService-1.0 user feature. The Activator.start() method first finds the HttpService using a service tracker, and then does something a bit sneaky:
Bundle systemBundle = bc.getBundle(Constants.SYSTEM_BUNDLE_LOCATION); BundleContext systemBC = systemBundle.getBundleContext(); ServiceTracker<Servlet, Servlet> st = new ServiceTracker<Servlet, Servlet> (systemBC, Servlet.class, new ServletTrackerCustomizer(systemBC)); st.open();
What we need to do here is to track all OSGi services implementing javax.servlet.Servlet. One option would be to track all org.osgi.service.subsystem.Subsystem instances as described in the Infocenter (and section 134.13 of the OSGi R5 Enterprise specification), and then install Servlet service trackers into each of them. We take a simpler approach here, and install the tracker against the system bundle, which represents the entire framework, including all application subsystems. (This ability to register ServiceTrackers against the system bundle in this way is a recent Equinox feature that we hope to see standardized in a future OSGi release.) Finally, the ServletTrackerCustomizer registers the servlets that it finds with the HttpService.
One drawback with the approach we’ve taken is that we’ve had to write a bundle activator, which we discourage. Bundle activators have been around for a long time in OSGi. They have historically been used as a place to set up service trackers, which is what we do in ours. Newer technologies such as Declarative Services and Blueprint provide better ways of tracking services, and we recommend their use. They generally require less code, are clearer and less error-prone than trackers, and often remove the need for a bundle activator altogether. This is a good thing – the best practice here is “don’t use bundle activators unless you have to.” Bundle activators are code that executes when the associated bundle is started. Once an activator exists it tends to grow over time, accumulating code that doesn’t properly belong there. This degrades overall code quality and increases bundle start time. In our case we have to use a bundle activator since there is no declarative way to install a tracker against the system bundle’s context.
Summary and future work
We’ve shown how to package HttpService into a Liberty user feature, and two different approaches to integrating it into OSGi Applications. The first method simply publishes an HttpService which applications must then register their servlets with. The second method finds all OSGi services with the javax.servlet.Servlet interface, and registers them with the HttpService. Both implementations suffer a drawback in that all servlets are registered under a single context root, /httpService. This is because there is only one HttpService instance for the entire server, and thus only one context root. A more sophisticated approach would be to construct an HttpService instance for each installed OSGi Application. This isn’t straightforward, since it would require the creation of a ‘synthetic’ or ‘virtual’ bundle by the user feature, but is entirely feasible.
We hope that this article has been of some use to anyone wanting to develop user features for the Liberty profile, and in particular those interested in using the HttpService. Do please contact the authors or post in our forums if you have any questions.