Extensible templates: the OSGi way

All the OSGi related contents we have seen so far during previous blog entries are related to backend systems. There is no other reason that my daily work basics happens under the services, sorry about that. I will try to correct this situation with an example on how we can build extensible user interfaces using the already builtin mechanisms into the platform.

Disclaimer: my most sincere apologies with all the damage my poor design skills could cause

The problem

We want to write a new Liferay application with an extensible UI, so third-party components can write their custom extensions, contributing to our UI with new elements (even replacing them)

Tracking extensions

Our solution will be based in the BundleTrackerCustomizer  concept and the Extender Pattern. We could use other approaches, based in services, but the extender pattern fits perfectly with our current template mechanism.

Using a BundleTrackerCustomizer we can get notified every time a new bundle is deployed into the OSGi container. This bundle will track all the extensions, indexed by name. The following class implements the tracking logic

 

As you can see in the previous class definition we are just interested in the plugins which present certain kind of attributes: the Templates-Location one. This attribute will contain the extension point with the location of the templates. Once the tracker detects this deployment, it will store the reference to all of this locations

Note this is just a proof of concept and need quite a few improvements. Hopefully we are going to use this idea to create some new applications and we will generalize this component with some nice features:

  • Create a component, so you can reuse it in your applications without writing your custom tracker.
  • Allow stackable modifications, so you can publish/unpublish components and go back/forward.
  • Named extensions: so you can define the extensions name and where you want to contribute. For example: Template-Extension: [extension_name, template_location]
  • Easier and powerfull definition of extensions, so you can contribute with multiple views to a single extension, so we can create lists and so on

Defining our extension points

Now we need to create our extensible views, defining those points where we want third-party components can contribute with their custom views.

Lets create a simple view file where we create a very simple extension:

 
  
       <div id="template-extensions">
                <#assign extensions = request.getAttribute("sampleProviderFactory").getRegisteredTemplates() />
 
                This is just a very simple test showing all the registered extensions contributed by plugins: <br />
 
                <#list extensions as extension>
                        <#include "${extension}" />
                </#list>
        </div>

As we can see in the preview snippet, we are just getting all the references to the existing contributions and including them. As you can imagine, we could do this extension mechanism much more powerful by using extension names and letting the contributions to decide which element they want to contribute. As I noted before, we will try to generalize this idea and create a customizable component.

We have a few limitations at the time of this writing I hope we can simplify at the future:

  • We live in an heterogeneus world where the OSGi components should live together with the core of the portal so, right now I am getting the references to the OSGi elements and making them accesible outside the container through static factory methods. I know this is not the best solution at all, but this is the unique approach we have for the 6.2 release
  • The FreeMarkerPortlet does not allow you to add new parameters to the template context so we have to pass it through the request or override the whole include method of the portlet.

They are not real problems but something we need to deal with in order to put our solution in place

Writing our first extension

Writing our first extension is a pretty simple task: we just need to create a new bundle, and define the mandatory attribute in order to be detected by the template tracker. You could use the following bnd file to create your extension

 
  
Bundle-Name: Sample Metrics Portlet Extesion
Import-Package: \
        com.liferay.portal.kernel.bean,\
        com.liferay.portal.kernel.log,\
        com.liferay.portal.kernel.servlet
Templates-Location:/templates
Web-ContextPath: /sample-metrics-portlet-extension
-wab: \
        docroot,\
        {WEB-INF/web.xml=docroot/WEB-INF/web.xml}
-wablib: \
        ${app.server.portal.dir}/WEB-INF/lib/util-java.jar
 
 
This is a simple overview of a small proof of concept we have done and which it is working pretty fine but we need to improve it a lot.  All the source code can be found here:
  • The shared/sample-metrics-core contains the core of our backend application
  • The portlets/sample-metrics-portlet contains the core of the UI application. The view.ftl is the extensible template built for this example. In addition, this component has the TemplateTracker artifact responsible of the extensions handling
  • The  portlets/sample-metrics-portlet-extension is just an extension to contribute to the core UI with a simple view

I have not gone through all the details since I just wanted to highlight the main concepts. Feel free to ping me if you want some low level details.

See you soon!