Blueprint and OSGi Fragments for granular module extension

Technical Blogs December 2, 2017 By Iacopo Colonnelli

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
  • 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!!!

 

Showing 1 result.