Blueprint and OSGi Fragments for granular module extension

Introduction

OSGi Fragments are a really powerful tool to extend the features of an existing bundle. Indeed, since fragments share classpath with their host bundle, they allow us to implement new functionalities avoiding unnecessary resource replications. Nevertheless, there are two main limitations that must be taken into account:

  • since fragments never reach the Active status, they are not able to neither register new services nor to override existing ones with an higher ranking
  • despite the classpath sharing feature, fragments cannot override host resources, because host context is searched before fragment context. Liferay JSP fragments are explicitly forced to behave differently by the JspFragmentTrackerCustomizer embedded into JspServlet, but this is not the default

So, what if we have to implement a new service that needs some existing resources located on a third-party bundle (i.e. a set of JSPs, a Soy template, language properties and so on)? Here comes the power of Blueprint framework. Indeed, if we declare our service via blueprint on our fragment, it will be injected into the host classpath when the fragment is attached and will be activated when the host bundle reaches it's Active state.

I will use the simple but practical example below in order to better clarify this concept.

Use case example

Few days ago, a friend of mine asked me how to extend a DDMFormFieldType component in order to make the indexable parameter configurable from UI. Therefore, such option is usually hidden by the visibilityExpression setting contained into DefaultDDMFormFieldTypeSettings implementation.

Obviously, in order to achieve our goal it's necessary to create a new DDMFormFieldTypeSettings class containing an indexType() method annotated with @DDMFormField(visibilityExpression = "TRUE") and tell our DDMFormFieldType component to refer to such settings file. The point here is: how can we do that best, by making the most of the OSGi paradigm modularity? Let's examine the possible (and not possible) solutions:

  • Create a new bundle that implements an higher ranked version of the DDMFormFieldType service. This is the most obvious solution, but with this implementation we are forced to duplicate also js resources and language properties. It should be our last choice, if we can't do anything better. But I'm sure we can ^_^
  • We don't want to replicate resources, so we should try with an OSGI Fragment and its wonderful classpath sharing feature. We could maybe create a fragment and override the settings file, like we did with Ext Plugin in previous versions of Liferay. This solution DOESN'T WORK, because the host context is searched before the fragment context, so original resources always take precedence
  • Therefore, we are forced to implement also our DDMFormFieldType service and inject the new settings file into it. It's no big deal after all. We can simply create our component and set an higher service ranking on it's properties, and Bob's your uncle. Again, this solution DOESN'T WORK because since fragments do not reach the Active state, their services are not started. As pointed out by Ray Augé in his comment below, the correct reason behind this behaviour is the way in which the Service-Component OSGi header of the host bundle is created at build time. Indeed, such header contains a precise list of component descriptors (instead of a more permissive "OSGI-INF/*.xml", for example). Since a fragment bundle cannot override the content of this header, only originally specified Declarative Services are actually activated.
  • Well, our last solution is the good one and it's.....a combination of OSGi Fragments and Blueprint Framework. What an astonishing turn of events!!! With this strategy, we only need 3 files to achieve our goal:
    • Our custom DDMFormFieldTypeSettings class, with @DDMFormField(visibilityExpression = "TRUE") annotation on indexType() method
    • Our DDMFormFieldType class, without the component annotation, that refers to our settings file
    • An xml file, contained into the src/main/resources/OSGI-INF/blueprint folder, where we register our DDMFormFieldType implementation as a service with an higher ranking than the original. A simple tutorial on how to define services with blueprint framework syntax can be found here

An implementation of this simple example, realised for Liferay 7 CE ga5, can be found at https://github.com/GlassOfWhiskey/liferay-blueprint-fragment-example. In our case, we extended the ParagraphDDMFormFieldType service, but the same can be done for the other field types. Unfortunately, Liferay 7 CE ga5 doesn't come with an OOTB implementation of Blueprint, so we have to first install all required bundles in order to make it work. This process deserves a dedicated section: the next one.

Blueprint integration

Since Liferay has not an integrated implementation of the Blueprint Framework, we have to install a third-party library. Despite Eclipse Gemini Blueprint is the reference implementation for the OSGi Blueprint service, we chose to integrate Apache Aries Blueprint libraries for a couple of reasons:

  • As reported by LPS-56983, nobody is taking care of the Eclipse Gemini Blueprint project and it's not evolving at all. On the contrary, Apache Aries seems to have a more active community contributing to it
  • Apache Aries suite appears interesting also for other features, as the OSGi Subsystems described by David H Nebinger in this blog post

In order to install Apache Aries Blueprint suite in our Liferay environment, we only need to copy few bundles into the deploy folder:

Into the org-apache-aries-blueprint folder of the example repo there is the set of bundles that we used to enable Blueprint framework on our Liferay instance. Therefore, in order to run the example, you only have to:

  • Copy bundles from org-apache-aries-blueprint folder to your ${LIFERAY_HOME}/deploy folder
  • Deploy the fragment module on your Liferay instance

If the Fragment is correctly resolved, trying to add a new paragraph field to a form and clicking on Show More Options, you should see something like this

Conclusions

With this article, I'd like to bring your attention on how the combination of Blueprint Framework and OSGi Fragments can enhance even more the Liferay extensibility, giving to developers a powerful, versatile and easy-to-use tool to extend Liferay core, that is fully supported by the OSGi standard. Nevertheless, if you can find any drawbacks with this approach or if you have better ideas on how to handle such scenarios, please share them.
Happy Liferay coding!!!

 

8
Blogs
Fantastic post! Very useful.
Have you tried subsystems already? A colleague did and it works only till he restarts the server. Than it doesn't come up anymore (forgot the exact error/problem).
Hi Christoph,
thank you for your positive feedback.
Concerning the Subsystems issue, are you experiencing problems with David's example or with a different esa package? In the first case, I suggest your colleague to add a comment below David's blog post, with the used Liferay version and some details about the error. If you're experiencing a problem with a different subsystem usage, maybe it would be better to open a dedicated thread on Liferay forum. In any case, I need more details to help you
Very useful post! I don't know yet if I'm going to use Blueprint (however this approach is quite interesting), but every new peek under the hood of new Liferay 7/DXP from a different perspective is worth a lot!
This statement is not quite accurate:
> Again, this solution DOESN'T WORK, because since fragments do not reach the Active state, their services are not started.

It doesn't really have anything to do with the fact that fragments only become resolved. Since the code of the fragment is on the classpath of the host bundle, as long as there is something to trigger the code, it will execute, including services.

The reason this doesn't work with DS is because DS requires the header `Service-Component` to provide a list (or glob) of the component descriptors to process. This list is typically created during build time. In OSGi this is called an OPT-IN mechanism (pretty much everything in OSGi is OPT-IN in order to have strong metadata).

Secondly, since the `Service-Component` header is not adopted by the host (you might say cannot be overridden a fragment; only a few bundle headers behave this way) that makes putting DS components in a fragment a little pointless.

However, if the host bundle had already been created with `Service-Component: OSGI-INF/*.xml` (i.e. with a glob that matches a collection of zip entries) then adding components in a fragment would work fine because these entries could be from fragments (and their classes too).

So it's not that blueprint is magic in this regard. It's OPT-IN mechanism is simply designed in such a way as to allow for the bean descriptors from fragments to be read by the extender.
BTW, if you go back to the thread about Aries Proxy 1.1+ I posted a solution to the problem there.
Hi again Ray,
I updated the comment about Aries Proxy in the original post. Moreover, I added the Aries Util bundle, that is necessary to correctly resolve Aries Proxy module (I forgot to include it on Github repo).
Thank you for your comment, because I searched for more details and I discovered the OSGi Weaving Hook specification, that looks quite interesting ^_^

Hi Iacopo,

 

Can we also override non-component classes ?

 

Basically I want to modify implementation of one of the methods of PipingServletResponse present in the util-taglib.  of Liferay 7.1 DXP

 

For this I created a fragment bundle

Fragment-Host: com.liferay.util.taglib;bundle-version="3.4.5"

 

The method is never invoked, possibly because like you mentioned "fragments cannot override host resources, because host context is searched before fragment context".

 

 

Therefore, I created "blueprint.xml" file in "\resources\OSGI-INF\blueprint" the fragment bundle.

 

 

Below is the entry in the blueprint.xml file 

 

<? xml version= "1.0" encoding= "UTF-8" ?> <blueprint xmlns= "http://www.osgi.org/xmlns/blueprint/v1.0.0"> <bean class= "com.liferay.taglib.servlet.PipingServletResponse" id= "com.liferay.taglib.servlet.PipingServletResponse" /> </blueprint>

 

 

But still, the method is not invoked, instead the method from the original taglib is getting invoked.

 

 

Wanted to know if I am missing something here ?

 

 

 

Thanks and Regards

Narsingh Pal