TOTD #131 explained how dynamic OSGi services can be easily created and deployed in GlassFish. The dynamism comes from the fact that the client keeps a reference to the service which is automatically refreshed if the bundle providing the service is refreshed. That tip used ServiceTracker API for the dynamic discovery of service which still uses some boilerplate code. In Java EE 6 environment, this can be further simplified by using CDI to inject the service and delegate all the service discovery/bind code to a CDI Qualifier available using CDI extensions in GlassFish 3.1.
Siva provides more details in his blog. But basically GlassFish 3.1 comes with a a standard CDI portable extension (org.glassfish.osgi-cdi) that intercepts deployment of hybrid applications that has components who have expressed dependencies on OSGi services. This CDI extension then takes care of discover, bind, inject, and track the service.
With this new CDI extension, the boilerplate code of dynamically tracking the service changes from:
ServiceTracker tracker = new ServiceTracker(context, Hello.class.getName(), null);
tracker.open();
Hello hello = (Hello) tracker.getService();
System.out.println(hello.sayHello("Duke"));
to
@Inject @OSGiService(dynamic=true) Hello hello;
System.out.println(hello.sayHello("Duke"));
No String-based and completely typesafe resolution of an OSGi service in a web application, how neat!
This will work in a "hybrid application" only, and not in pure web application, as BundleContext is required for dynamically tracking the service. Notice that by default the application is not ready to handle the dynamicity of OSGi environment and so "dynamic=true" need to be set explicitly on @OSGiService.
The complete source code for this application is available here. This project consists of 4 Maven modules:
- helloworld-api
- helloworld-impl
- helloworld-client
- helloworld-cdiclient
The first three modules are explained in detail at TOTD #131. Lets create "helloworld-cdiclient" module!
- In the parent directory of TOTD #131 zip file, create a new Maven module as:
mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=org.samples.osgi.helloworld -DartifactId=helloworld-webclientMake sure to change the all the references from "totd131" to "totd154".
- The updated directory structure looks like:
osgi.properties pom.xml src src/main src/main/java src/main/java/org src/main/java/org/samples src/main/java/org/samples/osgi src/main/java/org/samples/osgi/helloworld src/main/java/org/samples/osgi/helloworld/App.java src/main/resources src/main/webapp src/main/webapp/index.jsp src/main/webapp/WEB-INF src/main/webapp/WEB-INF/beans.xml- "index.jsp" is generated by the Maven archetype
- "beans.xml" is a newly added file to enable CDI injection
- "osgi.properties" is a newly added file and is used to read the metadata for OSGi hybrid application
- "App.java" is a Servlet that invokes the service
- The "osgi.properties" file looks like:
Web-ContextPath:/helloworld-cdiclientThis makes sure that the hybrid application is accessible at "/helloworld-cdiclient" context root.
- The updated "pom.xml" looks like:
<?xml version="1.0" encoding="UTF-8"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <artifactId>totd154</artifactId> <groupId>org.samples.osgi.helloworld</groupId> <version>1.0-SNAPSHOT</version> </parent> <packaging>war</packaging> <groupId>org.samples.osgi.helloworld</groupId> <artifactId>helloworld-cdiclient</artifactId> <version>1.0-SNAPSHOT</version> <name>helloworld-cdiclient</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.samples.osgi.helloworld</groupId> <artifactId>helloworld-api</artifactId> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>6.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.glassfish</groupId> <artifactId>osgi-cdi-api</artifactId> <version>3.1-SNAPSHOT</version> <scope>provided</scope> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>2.1.0</version> <extensions>true</extensions> <configuration> <supportedProjectTypes> <supportedProjectType>ejb</supportedProjectType> <supportedProjectType>war</supportedProjectType> <supportedProjectType>bundle</supportedProjectType> <supportedProjectType>jar</supportedProjectType> </supportedProjectTypes> <instructions> <!-- Read all OSGi configuration info from this optional file --> <_include>-osgi.properties</_include> <!-- By default, we don't export anything --> <Export-Package>!*.impl.*, *</Export-Package> </instructions> </configuration> <executions> <execution> <id>bundle-manifest</id> <phase>process-classes</phase> <goals> <goal>manifest</goal> </goals> </execution> <execution> <id>bundle-install</id> <phase>install</phase> <goals> <goal>install</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> <plugin> <!-- Need to use this plugin to build war files --> <artifactId>maven-war-plugin</artifactId> <groupId>org.apache.maven.plugins</groupId> <!-- Use version 2.1-beta-1, as it supports the new property failOnMissingWebXml --> <version>2.1-beta-1</version> <configuration> <archive> <!-- add bundle plugin generated manifest to the war --> <manifestFile> ${project.build.outputDirectory}/META-INF/MANIFEST.MF </manifestFile> <!-- For some reason, adding Bundle-ClassPath in maven-bundle-plugin confuses that plugin and it generates wrong Import-Package, etc. So, we generate it here. --> <manifestEntries> <Bundle-ClassPath>WEB-INF/classes/ </Bundle-ClassPath> </manifestEntries> </archive> <!-- We don't have a web.xml --> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </pluginManagement> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> </plugin> </plugins> </build> </project>Screencast #32 provide a detailed explanation of this "pom.xml". A basic introduction to "hybrid applications" is given here.
- The updated "App.java" looks like:
package org.samples.osgi.helloworld; import java.io.IOException; import java.io.PrintWriter; import javax.inject.Inject; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.glassfish.osgicdi.OSGiService; import org.samples.osgi.helloworld.api.Hello; /** * Hello world! */ @WebServlet(urlPatterns = {"/HelloWebClient"}) public class App extends HttpServlet { @Inject @OSGiService(dynamic=true) Hello hello; @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { PrintWriter out = response.getWriter(); out.println(hello.sayHello("Duke")); } }This is a "web.xml"-free servlet using @WebServlet. "@OSGiService" is used to dynamically discover, bind, inject, and track the service. So if the backend service changes then the service reference is automatically updated without any additional effort.
- Build and Deploy this project as:
mvn clean install asadmin deploy --type=osgi target/helloworld-cdiclient-1.0-SNAPSHOT.warOptionally you can also drop this generated WAR file to "glassfish/domains/domain1/autodeploy/bundles" directory as well.
- Run the app
- Make sure helloworld-api-* and helloworld-impl-* bundles are already deployed as explained in TOTD #131.
- Invoking "curl http://localhost:8080/helloworld-cdiclient/HelloWebClient" shows the result as "Hello Duke".
- Change "org.samples.osgi.helloworld.impl.HelloImpl" to use "Howdy" string instead of "Hello", build the bundle again (mvn clean install), and copy it to the "glassfish/domains/domain1/autodeploy/bundles" directory.
- Invoking "curl http://localhost:8080/helloworld-cdiclient/HelloWebClient" now shows the result as "Howdy Duke". This ensures that the service changes are dynamically tracked.
I tried this with GlassFish 3.1 build 35 Web Profile.
Read the latest about GlassFish and OSGi integration on this wiki.
Where are you deploying your OSGi applications ?
Technorati: totd osgi javaee6 glassfish dynamic discovery
Related posts:- TOTD #131: Dynamic OSGi services in GlassFish – Using ServiceTracker
- TOTD #124: OSGi Declarative Services in GlassFish – Accessed from a Java EE client
- TOTD #130: Invoking a OSGi service from a JAX-WS Endpoint – OSGi and Enterprise Java
- TOTD #36: Deploy OSGi bundles in GlassFish using maven-bundle-plugin
- TOTD #118: Managing OSGi bundles in GlassFish v3 – asadmin, filesystem, telnet console, web browser, REST, osgish
Similar screencast here: http://blogs.sun.com/alexismp/entry/screencast_dynamic_osgi_services_using
Comment by Alexis MP — January 11, 2011 @ 5:44 am