Leveraging OSGi to Create Extensible Applications for Liferay 6.2

It was great to participate in the Liferay North American Symposium this year. With hundreds of Liferay users (customers, partners, community members...) and dozens of presentations, it was not only a huge success but also a great opportunity to share user experiences and get your feedback. North American Symposium is over, but Liferay World Tour 2014 is not! There are still many important events in our calendar so you still have the chance to learn about Liferay latest features firsthand. 
 
Julio Camarero and I will be talking about Extensible Liferay Applications in the Spanish Symposium next week and in the Developer Conference in early November. This is probably one of the most relevant features in Liferay 6.2 because it's meant to completely change how Liferay applications are developed. Let's find out how with a simple example:
 

A Shipping Cost Calculator

Suppose you have an online shop and you need an application to calculate the final cost of purchasing an item, including its shipping to destination and considering not only the distance but also the currency, the local taxes and any other particularities. Thus, the final cost would be:
 
Final cost = [no. of items x item price] + [shipping cost to selected destination]
 
As a developer you could implement a very complex application that contains all possible shipping destinations. Every time you wanted to add or modify a shipping destination, you’d have to release a new version of your application. And likely your application would be more and more complex with every new release.
Alternatively, you could implement just the core functions of your calculator and define the shipping destinations as extensions to your application. This way, if you needed to add or modify a shipping destination those changes would not affect to the core functions, but only to an specific extension. With this approach, the release frequency of your core application as well as its complexity would decrease. Instead, new features would be added through small extensions with their own release frequency.

Modular and Extensible Applications: the OSGi Way

Probably at this point you’ve already realized the benefits of the second approach: 
  • Simpler maintenance of the core application by reducing its complexity
  • Better performance (only required extensions would be installed)
  • Support for third party extensions
  • New market opportunities (e.g. purchasing shipping extensions)
This type of modular and extensible applications are defined by the OSGi  (Open Service Gateway initiative) specification. Thanks to Liferay support for OSGi since version 6.2, you can now apply this pattern to your plugins. 
 
We recommend you to go through the documentation about OSGi apps in Liferay. For now we’ll show some quick guidelines to apply this pattern to the Shipping Cost Calculator project. You can also have a look to the complete source code of this project.
 

Required Services for an Extensible Shipping Cost Calculator

OSGi services consist of:
  • An interface, defining the service “contract”
  • One or more implementations of the interface
To make our shipping cost calculator extensible, we need two types of OSGi services:
 

Shipping Extensions:

The ShippingExtension interface contains the methods that any shipping extension must implement. Implementations of this interface (e.g. ShippingExtensionUSA) are annotated with @Component, which allows OSGi to dynamically detect a new shipping extension when it’s deployed.
@Component(immediate = true, service = ShippingExtension.class)
public class ShippingExtensionUSA implements ShippingExtension {
 

ShippingExtension Registry

In order to have an up-to-date list with all the available shipping options, we need to track when these extensions (annotated with @Component) are deployed or undeployed. Through the @Referecene annotation the registerShippingExtension method of ShippingExtensionRegistryImpl is bound to the ShippingExtensionService, so it will be invoked every time an implementation of ShippingExtension is deployed. The unregisterShippingExtension method is called when an implementation is undeployed.
@Reference(
	unbind = "unregisterShippingExtension",
	cardinality = ReferenceCardinality.MULTIPLE,
	policy = ReferencePolicy.DYNAMIC)	
public void registerShippingExtension(ShippingExtension shippingExtension) {
	_shippingExtensions.put(
		shippingExtension.getShippingExtensionKey(), shippingExtension);
}

 

Accessing OSGi services from a non OSGi context: the ServiceTrackerUtil

We’re almost done. All we need to do is to list the shipping extensions registered by the ShippingExtensionRegistry in our GUI and process the resulting form according to the selected option. Since our GUI is still a Liferay portlet, which is not handled by the OSGi service container (yet), we cannot use the @Reference annotation to obtain the ShippingExtension service. Liferay provides a util class for this purpose: the ServiceTrackerUtil.
_shippingExtensionRegistry = ServiceTrackerUtil.getService(
	ShippingExtensionRegistry.class, bundle.getBundleContext());
 
You can now test the app. First deploy all modules to your Liferay server, except for the shipping extensions. Then browse any site page and add the Shipping portlet. Notice that the calculator is functional, but it displays no shipping options. Now deploy the shipping extensions one by one, refreshing the page every time. You’ll now see a list with the available shipping extensions. Selecting a shipping extension will modify the form and the final result.
 
 

Going even deeper in modularization

If you have worked with the sample code, you may have noticed that the core application is not contained in a single project, but in three: 
  • shipping-api: Contains only the interfaces of the OSGi services that make up the app
  • shipping-impl: Contains the implementation of the core OSGi services of the app
  • shipping-web: Contains the user interface of the app
With this approach, the core of application can be easily modified by changing the implementation or the web interface, without changing the public API. 
 
Audience Targeting is the first official Liferay application that is built following this OSGi way, but this is actually how all Liferay apps will be in the next version of Liferay Portal so WELCOME TO THE FUTURE!!
Blogues
Thanks for the interesting pointers.
Liferay 6.2 (at least SP8) doesn't seem to contain the ServiceTrackerUtil, wondering if it would be wise to use the one in contained in the example and plug it in .
The ServiceTrackerUtil is not a part of our example, but it's contained in the com.liferay.osgi.util module. As you mentioned, this module is not in Liferay 6.2, but that doesn't mean that you cannot use it in that version.

This generic module is provided through Liferay's repository at http://repository.liferay.com/nexus/index.html#nexus-search;quick~liferay.osgi.util. You can obtain it from there and deploy it in Liferay 6.2, so that other OSGi plugins can use its utility classes. In our example, we do this automatically with the shipping-runtime-dependencies plugin.

By the way, the ServiceTrackerUtil has been replaced in the latest version of com.liferay.osgi.util by the ReflectionServiceTracker. We'll try to update our example to use it, but in the meantime take it into account.

Thanks!
With 6.2 deployment I see
Preventing the replacement of the plugin class loader
Preventing the removal of the plugin class loader
,haven't dwelt in much, but if someone already has an answer emoticon
Moving ahead :
java.lang.NullPointerException: A null service reference is not allowed.
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.getService(BundleContextImpl.java:586)
at com.liferay.osgi.util.service.ServiceTrackerUtil$1.invoke(ServiceTrackerUtil.java:47)