This story appeared on JavaWorld at
http://www.javaworld.com/javaworld/jw-06-2008/jw-06-osgi3.html
The first two articles in the Hello, OSGi series have laid the groundwork for understanding how the OSGi service platform works. In this final installment, you'll learn how OSGi's pluggable, component-based development model applies to Web applications. You'll build a bundles-based OSGi application that can be used to serve static resources, servlets, or JSPs. You'll also gain hands-on experience using the Equinox framework to easily leverage OSGi's modularity, versioning, and dynamic services in your Web applications.
The first article is this series, "Hello, OSGi, Part 1: Bundles for beginners ," introduced OSGi development concepts and showed you how to build a simple Hello World application using Equinox, Eclipse's OSGi container implementation. "Hello, OSGi, Part 2: Spring Dynamic Modules " introduced Spring DM, which makes it very easy to build Spring applications that can be deployed in an OSGi container. This final article in the series tackles the question being asked by many developers right now: I like the concept of OSGi, but how do I use it to build Web applications?
As you'll learn in this article, OSGi's development model greatly simplifies the process of writing truly pluggable, modular, and componentized server-side applications. Development exercises will be based (as they have been throughout this series) on the OSGi container reference implementation, Equinox. This time, however, we'll be using Equinox on the server side. We'll begin by discussing the options for deploying your OSGi Web application (in this case Jetty or Tomcat), followed by a short tutorial in setting up your Web application development environment. You will then develop a simple Hello World OSGi Web app and experiment with the different ways to deploy it.
Although the Server-Side Equinox project is in the early stages of development, it is quickly growing. There is a move to add support for popular MVC frameworks such as Apache Struts and the Spring Web framework, along with popular object-relational mapping tools such as Hibernate. The Rich Ajax Platform is another Eclipse project that allows you to use Ajax applications with server-side Equinox. |
There are two ways to deploy OSGi Web applications using Server-Side Equinox : You can embed a lightweight servlet container such as Jetty inside your OSGi container, or you can embed your OSGi container inside a Web application, and then deploy that application in a servlet container such as Apache Tomcat .
Now, here's the good news: the process of developing an OSGi Web application is the same no matter which approach you choose. Thus, it's best to embed the servlet container inside your OSGi container during the development process; once your application is ready, you can re-package and deploy it if you wish. You'll learn how to embed a servlet container inside an OSGi container in just a moment. First we need to set up your development environment.
For this article's application development exercise you will wrap a lightweight servlet container inside an OSGi bundle (or plug-in , in the Eclipse context). Once this plug-in is installed in your OSGi container, it will listen for HTTP requests. Upon receiving a request it will check to see if any of the installed plug-ins can handle it and, if so, will then forward control to the appropriate plug-in to generate a response. Once the response has been generated, the OSGi container will forward it to the client.
Server-Side Equinox simplifies OSGi-based Web application development by providing a couple of readymade plug-ins that wrap a servlet container inside an OSGi plug-in. Each of the following plug-ins has an embedded servlet container:
org.eclipse.equinox.http
: A plug-in with a very small footprint, suitable for resource-constrained environments. org.eclipse.equinox.http.jetty
: Uses Jetty as the underlying engine for providing Servlet API 2.4 support. In this section, you will set up your OSGi Web application development environment by embedding the Jetty plug-in inside your Eclipse IDE. (You should start up your IDE now if you haven't already.)
Your development environment is ready to go. The next step is to execute your bundles in the Equinox OSGi Framework. Revisit the first part of this series if you don't remember how to do that.
<!-- -->
Once you've executed the bundles in the Equinox OSGi Framework, you will see the OSGi>
prompt in the console view. You should be able to see messages generated by the Jetty servlet container in the OSGi console; they'll look like the ones in Listing 1.
osgi> May 21, 2008 12:28:06 PM org.mortbay.http.HttpServer doStart
INFO: Version Jetty/5.1.x
May 21, 2008 12:28:07 PM org.mortbay.util.Container start
INFO: Started org.mortbay.jetty.servlet.ServletHandler@f47bf5
May 21, 2008 12:28:07 PM org.mortbay.util.Container start
INFO: Started HttpContext[/,/]
May 21, 2008 12:28:07 PM org.mortbay.http.SocketListener start
INFO: Started SocketListener on 0.0.0.0:80
May 21, 2008 12:28:07 PM org.mortbay.util.Container start
INFO: Started org.mortbay.http.HttpServer@1f6df4c
Wait until you see the message INFO: Started org.mortbay.http.HttpServer@1f6df4c
before moving on to the next step.
You can verify that you can access the Jetty server by pointing your browser to http://localhost/. You should see an error page with the messages in Listing 2.
HTTP ERROR: 404
ProxyServlet: /
RequestURI=/
Powered by Jetty://
You've received a 404 error because Jetty is unable to find the requested resources. The Jetty OSGi plug-in does not have any default index page that it serves when it is unable to find a registered plug-in for handling the current request; it throws this 404 page instead.
Assuming your setup matches mine so far, you're ready for your first exercise in using Server-Side Equinox to develop an OSGi-based Web application.
The application for this exercise will be an OSGi bundle that contains two resources. The first is helloworld.html, which is a static HTML file, and the second is HelloWorldServlet
, which is an HttpServlet
. It's important to note that the OSGi container has an HttpService
OSGi service. Every bundle that wants to handle HTTP requests calls methods on this service to tell the OSGi container what URLs it can handle. There are two ways to register the URLs an OSGi bundle can handle:
HttpService
from OSGi's service registry, then call methods on it to register the request URLs that the bundle can service. We'll walk through both of these techniques, starting with the programmatic registration approach.
Follow these steps to programmatically register URLs that your plug-in can handle.
com.javaworld.sample.osgi.web.programmatic
. (See "Developing a Hello World bundle " in the first article in this series for more about creating OSGi plug-ins in Eclipse.) com.javaworld.sample.osgi.web.programmatic
plug-in and modify it so that it imports the javax.servlet
, javax.servlet.http
, org.osgi.service.http
, and org.osgi.util.tracker
packages. After making these changes, your MANIFEST.MF should look like what you see in Listing 3. Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Webapp Plug-in
Bundle-SymbolicName: com.javaworld.sample.osgi.web.programmatic
Bundle-Version: 1.0.0
Bundle-Activator: com.javaworld.sample.osgi.web.webapp.Activator
Bundle-Vendor: JAVAWORLD
Bundle-Localization: plugin
Import-Package: javax.servlet;version="2.4.0",
javax.servlet.http;version="2.4.0",
org.osgi.framework;version="1.3.0",
org.osgi.service.http;version="1.2.0",
org.osgi.util.tracker;version="1.3.2"
Import-Package
manifest header defines the list of packages that you want to import. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>HelloWorld OSGi Web</title>
</head>
<body>
<h3>Hello From helloworld.html</h3>
</body>
</html>
HelloWorldServlet
shown in Listing 5. package com.javaworld.sample.osgi.web.webapp;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloWorldServlet extends HttpServlet{
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
resp.getWriter().println("<h3>Hello from HelloWorldServlet</h3>");
}
}
HelloWorldServlet
class extends HttpServlet
and overrides its doGet()
method. The only thing the new doGet()
method does is write "Hello from HelloWorldServlet
" in the output. com.javaworld.sample.osgi.web.programmatic
plug-in. Activator.java
, which will act as a bundle activator for this plug-in, is shown in Listing 6. import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
public class Activator implements BundleActivator {
ServiceTracker httpServiceTracker;
public void start(BundleContext context) throws Exception {
System.out.println("Hello World!!");
httpServiceTracker = new HttpServiceTracker(context);
httpServiceTracker.open();
}
public void stop(BundleContext context) throws Exception {
System.out.println("Goodbye World!!");
httpServiceTracker.close();
httpServiceTracker = null;
}
}
Activator
class extends BundleActivator
and implements two methods:
start()
: The start()
method will be called when the OSGi container starts this plug-in. Inside the start()
method, you create an object of class HttpServiceTracker
; this is the ServiceTracker
class that you'll use to track HttpService
. Once you have an object of class HttpService
, you call its open()
method to start tracking the HttpService
. stop()
: The OSGi container will call the stop()
method when it's time to shut down the plug-in. Inside the stop()
method, you call the HttpServiceTracker
object's close()
method to stop tracking HttpService
. HttpServiceTracker
class, shown in Listing 7. import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpService;
import org.osgi.util.tracker.ServiceTracker;
public class HttpServiceTracker extends ServiceTracker{
public HttpServiceTracker(BundleContext context) {
super(context, HttpService.class.getName(), null);
}
public Object addingService(ServiceReference reference) {
HttpService httpService = (HttpService) context.getService(reference);
try {
httpService.registerResources("/helloworld.html", "/helloworld.html", null);
httpService.registerServlet("/helloworld", new HelloWorldServlet(), null, null);
} catch (Exception e) {
e.printStackTrace();
}
return httpService;
}
public void removedService(ServiceReference reference, Object service) {
HttpService httpService = (HttpService) service;
httpService.unregister("/helloworld.html");
httpService.unregister("/helloworld");
super.removedService(reference, service);
}
}
<!-- -->
HttpService
is the OSGi service that allows bundles in the OSGi environment to dynamically register and unregister both resources and servlets in the HttpService
's URI namespace -- in other words, to map request URIs to either a static HTML file or to an HttpServlet
. The HttpServiceTracker
class is an object of type ServiceTracker
, which simplifies the tracking of HttpService
. (See "Tracking services " in the first article in this series for more about OSGi's ServiceTracker
.)
The HttpServiceTracker
class shown in Listing 7 overrides two methods: addingService()
and removedService()
. They're worth explaining here:
HttpService
is available. Inside this method, you first call HttpService.registerResources("/helloworld.html", "/helloworld.html", null)
to map the helloworld.html file to /helloworld.html. After this, whenever you request http://localhost/helloworld.html, the HttpService
will serve helloworld.html to the user. Please note that you needn't map helloworld.html to the /helloworld.html URL; the filename doesn't need to match the address, and you could map it to something like /test.html instead.HttpService.registerResources("/html", "/html", null)
. If you then wanted to access test.html inside the html folder, the appropriate address would be http://localhost/html/test.html. The registerServlet()
method is used for mapping a URL to the HttpServlet
class. In the sample code, a call to registerServlet("/helloworld", new HelloWorldServlet(), null, null)
is used to map the /helloworld URL to the HelloWorldServlet
class. If you want to pass initialization parameters to your HttpServlet
, then you can create an java.util.Dictionary
object and pass it as third argument to registerServlet()
. addingService()
method in your ServiceTracker
to get a service, you should also override removedService()
to unget that service. Inside the removedService()
method, you call the unregister()
method to unregister both the /helloworld.html and the /helloworld URI. This informs HttpService
that com.javaworld.sample.osgi.web.programmatic
no longer wishes to serve requests for the given URIs. If you call the unregister()
method to unregister the servlet, then the destroy()
method of that servlet will be called to give it a chance to clean itself up. Now the HelloWorld OSGi Web application is ready and you can execute all your bundles in the Equinox OSGi Framework. You should be able to access helloworld.html at http://localhost/helloworld.html, and access the HelloWorld servlet at http://localhost/helloworld.
As you've probably noted, programmatically registering the request URLs that your OSGi plug-in can handle is no small undertaking. Further, if you ever wanted to change the URL for helloworld.html -- from /helloworld.html to /hello.html, say -- you would have to update HttpServiceTracker.java
, re-compile your code, and then deploy it in the OSGi container. Next, we'll take a look at the declarative approach, which is a bit easier.
com.javaworld.sample.osgi.web.declarative
. Choose OSGi Equinox Framework as the target platform. com.javaworld.sample.osgi.web.declarative
bundle so that it imports the javax.servlet
and javax.servlet.http
packages and makes org.eclipse.equinox.http.registry
a required bundle for this bundle. After making these changes, your MANIFEST.MF file should look like Listing 8. Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Declarative Plug-in
Bundle-SymbolicName: com.javaworld.sample.osgi.web.declarative;singleton:=true
Bundle-Version: 1.0.0
Bundle-Vendor: JAVAWORLD
Bundle-Localization: plugin
Import-Package: javax.servlet;version="2.4.0",
javax.servlet.http;version="2.4.0"
Require-Bundle: org.eclipse.equinox.http.registry
Require-Bundle
manifest header contains a list of bundle symbolic names that need to be searched after the imports are searched but before the bundle's classpath is searched. However, only packages that are marked as exported by the required bundles are visible to the requiring bundle. com.javaworld.sample.osgi.web.programmatic
bundle to the com.javaworld.sample.osgi.web.declarative
bundle. com.javaworld.sample.osgi.web.declarative
bundle to register all the requests it can handle, as shown in Listing 9. <?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>
<extension-point id="servlets" name="HttpService servlets" schema="schema/servlets.exsd"/>
<extension-point id="resources" name="HttpService resources" schema="schema/resources.exsd"/>
<extension-point id="httpcontexts" name="HttpService httpcontexts" schema="schema/httpcontexts.exsd"/>
<extension
id="helloServlet"
point="org.eclipse.equinox.http.registry.servlets">
<servlet
alias="/decl/helloworld"
class="com.javaworld.sample.osgi.web.webapp.HelloWorldServlet">
</servlet>
</extension>
<extension
id="helloResource"
point="org.eclipse.equinox.http.registry.resources">
<resource
alias="/decl/helloworld.html"
base-name="/helloworld.html"
/>
</extension>
</plugin>
Note that plugin.xml has two <extension>
elements. The first, with an id
attribute whose value is helloServlet
, says that the HelloWorldServlet
class should be used for handling /decl/helloworld
requests. By setting the value of the point
attribute to org.eclipse.equinox.http.registry.servlets
, you're indicating that this is the servlet class. The second <extension>
element, with an id
attribute given a value of helloResource
, indicates that user requests for /decl/helloworld.html
should return the helloworld.html file to the user.
Now your HelloWorld OSGi Web application, rebuilt using a declarative approach, is ready, and you can execute all your bundles in the Equinox OSGi Framework. You can access helloworld.html at http://localhost/decl/helloworld.html and access the HelloWorld servlet at http://localhost/decl/helloworld.
<!-- -->
Executing the Equinox OSGi container within the Eclipse IDE is convenient for development. Once your application is ready for deployment, however, you will want to run your OSGi container outside Eclipse. Follow these steps to launch your Equinox OSGi container from the command line.
Now, open the command console and change directories to C:\equinox, then execute the following command:
<!-- --><!-- -->java -jar org.eclipse.osgi_<version>.jar -console
The Equinox OSGi container will start, and you will see the OSGi>
prompt.
Note that even though you copied all the plug-in JARs to the C:\equinox directory, none of those plug-ins have been installed into the OSGi container yet. You have to install them one by one. You can install a plug-in in the OSGi container with the following command syntax:
install file:<pathtoplug-injar>
For example if you want to install the javax_servlet v2_4 plug-in, then you will have to execute the following command:
install file:javax.servlet_2.4<version>.jar
Use the install
command to install all the necessary plug-ins now.
Once all of your plug-ins are installed, you can verify them by executing the ss
command. The OSGi console will display a three-column table listing all the installed plug-ins: the first column displays the ID of the plug-in, the second the state of the plug-in, and the third the name of the bundle.
Notice that all the bundles are in the INSTALLED
state except for the org.eclipse.osgi
bundle, which is in the ACTIVE
state. So, the next thing you need to do is start all of the plug-ins. You can start a plug-in by using the following command:
start <pluginid>
In this case, the ID the javax.servlet
v2_4 bundle is 1, so you can start it like so:
start 1
Once all the plug-ins are started, you can verify them by invoking the ss
command. The output on your OSGi console should look like what you see in Figure 2.
Finally, you can test whether your OSGi Web application is set up properly by pointing your browser to http://localhost/helloworld.html. You should see the message "Hello from helloworld.html".
<!-- -->
In this section, you will see another deployment approach for an OSGi Web application, which is to embed an Equinox OSGi container inside a Web application and then deploy that Web application inside a servlet container such as Apache Tomcat. The Equinox Framework provides bridge.war , a template Web application that has an Equinox OSGi container embedded inside it.
The bridge.war application contains the org.eclipse.equinox.servletbridge.BridgeServlet
servlet. This servlet takes care of initializing your OSGi container. Whenever it receives a client request, it checks to see if there is an OSGi plug-in that can handle the request; if there is, it forwards control to that plug-in. The following steps will demonstrate how you can use the com.javaworld.sample.osgi.web.programmatic
and com.javaworld.sample.osgi.web.declarative
plug-ins inside bridge.war.
osgi>
prompt in the Tomcat console. ss
to display a list of all the OSGi bundles installed in bridge.war. Make sure that the com.javaworld.sample.osgi.web.declarative
and com.javaworld.sample.osgi.web.programmatic
plug-ins are installed in OSGi container; if they aren't, install them by executing the install file:<jarfilename>
command. start <bundleid>
command at the OSGi prompt. The Bridge application provides the following URLs that you can use to control the OSGi container from a browser. (These should be appended to http://localhost:8080/bridge/ for this example; this URL could change based on your Tomcat install.)
Go ahead and play with the application -- and give yourself a pat on the back: you've built your first OSGi Web application!
Although OSGi's roots are in embedded and client-side solutions, many believe it will yield the greatest rewards in server-side development. Server-Side Equinox is one initiative that makes it far easier to apply OSGi's pluggable, component-based development model to Web applications. (SpringSource Application Platform is another, but well beyond the scope of this article.) By following the examples in this article, you've taken your first steps toward developing your own OSGi-based Web applications. You've set up your OSGi Web application development environment in Eclipse; learned about both programmatic and declarative approaches to registering OSGi bundles; and walked through the two different approaches to deploying your OSGi-based Web application: embedding a Jetty servlet container plug-in into Equinox, and embedding Equinox into Tomcat.
In this series you've learned enough to get started with building OSGi-based applications. You've learned about OSGi's development model and built a simple client-server application consisting of two bundles. You've learned how Spring Dynamic Modules makes it easier to build Spring applications in an OSGi container. And you've learned how Server-Side Equinox provides an accessible framework for developing and deploying OSGi applications in a server environment. All of these hands-on exercises have served to get you started with OSGi-based development. In the process, you've likely grappled with the concept of Java modularity, as many developers are doing right now. Hopefully this series has helped you to better understand OSGi and Java modularity and see where it fits into your future Java development projects. Visit the Resources section to learn more about OSGi .
Sunil Patil is a Java Enterprise/Portlet developer working for Ascendant Technology in San Francisco, California. He is the author of Java Portlets 101 (SourceBeat, April 2007) and has written numerous articles published by O'Reilly Media. Sunil was a member of IBM's WebSphere Portal Server development team for three years and is actively involved in the Pluto community. In addition to being an IBM Certified WebSphere Portal Server Application Developer for both v5.0 and v5.1, he is a Sun Microsystems Certified Java Programmer, a Web component developer, and a business component developer. You can view Sunil's blog at http://jroller.com/page/SunilPatil .
All contents copyright 1995-2008 Java World, Inc. http://www.javaworld.com