How to Inject OSGi Dependencies in Custom Portlets in Liferay 7

This article was created in partnership with Ktree. Thank you for supporting the partners who make SitePoint possible.

Liferay 7/DXP leverages the OSGi framework to provide a development environment for modular applications. Recently we used this feature to inject Liferay standard module functionality into our custom portlet.

We achieved this by injecting wiki assets as a dependency into our custom portlet. For this article’s purposes we will use one sample custom module which displays one field text area, and below we will inject wiki assets onto the screen.

--ADVERTISEMENT--

Let’s go through few concepts before we start the actual program.

Brief About OSGI

OSGi (Open Services Gateway Initiative) allows you to divide your application into multiple modules, and thus more easily manage cross-dependencies between them. Liferay uses the OSGi container implementation of Equinox.

The main advantages of OSGi:

  • You can install, uninstall, start, and stop different modules of your application dynamically without restarting the container.
  • Your application can have more than one version of a particular module running at the same time.
  • OSGi provides very good infrastructure for developing service-oriented applications, as well as embedded, mobile, and rich internet apps.

For more info about OSGi, check out these articles:

Liferay 7/DXP supports these types of dependency currently. This is something you need to specify in your build.gradle file, which you will doing in Step 1. This tutorial focuses only on 'Compile' and 'CompileOnly'.

Compile

The dependencies required to compile the production source of the project. As you can see in the later part of this article, for this dependency you need to configure it in both build.gradle and bnd.bnd.

CompileOnly

Dependencies required at compile time, but never required at runtime. As the wiki assets are already in the OSGi container, we will use this option. Adding this to build.gradle is sufficient.

RunTime

The dependencies required by the production classes at runtime. By default, also includes the compile time dependencies.

Provided

A dependency in the provided scope that is needed for compilation of a project, but it should not be distributed with it.

Along with dependency type, we need to specify a few more parameters which are described below.

  • group – typically, this is your organization name or the project name.
  • name – the project name.
  • version – the version number.
  • fileTree – to provide dependencies from the local file system.

With these concepts under our belts, we are ready to write some code, so let's fire up your Eclipse Liferay IDE.

Step 1: Create the Liferay Workspace Project

Create your Liferay Workspace project by following this link.

Step 2: Create Liferay Module

This builds the build.gradle file. Edit build.gradle by adding the dependencies for our development purposes. See the example code.

To create a Liferay Module in the IDE, select File > New > Liferay Module Project.

Example: The build.gradle file

dependencies {
        compileOnly group: "com.liferay", name: "com.liferay.wiki.api", version: "2.1.0"
        compile group: 'org.jsoup', name: 'jsoup', version: '1.8.3'
        compile group: 'commons-io', name: 'commons-io', version: '2.0.1'
        compile group: "velocity-tools", name: "velocity-tools", version: "1.4"
        compile group: "org.apache.velocity", name: "velocity", version: "1.6.4"
        compile fileTree(dir:"lib",include:"*.jar")
}

Note: Since this package (com.liferay.wiki.api) is already loaded in the container, so we have to use the option of CompileOnly.

Step 3: Edit bnd.bnd

Edit the bnd.bnd file by adding the dependencies for Compile dependencies. Eclipse would have generated this file automatically.

Example: bnd.bnd file

We can include the jar in two different ways, both options are given below.

Option 1:

* -includeresource:

This header is used to add external dependencies into our module. The following syntax extracts the jar and adds packages to our module.

-includeresource: \
@jsoup-1.8.3.jar,\
@opencsv-2.2.jar,\
@commons-io-2.0.1.jar,\
@velocity-[0-9]*.jar,\
@velocity-tools-[0-9]*.jar

Option 2:

The following syntax adds the jars to our module:

-includeresource: \
lib/jsoup.jar=jsoup-1.8.3.jar,\
lib/opencsv.jar=opencsv-2.2.jar,\
lib/velocity.jar=velocity-[0-9]*.jar,\
lib/velocity-tools.jar=velocity-tools-[0-9]*.jar

Since we have made com.liferay.wiki.api in the .gradle file CompileOnly, we have to import these packages by adding these two lines in the bnd.bnd file. If you don’t add them, bndtool will scan the project and add to the manifest file.

com.liferay.wiki.model;version="[1.0,2)",
com.liferay.wiki.service;version="[1.1,2)"

Step 4: Run Eclipse Build

Now run Eclipse Build using the path eclipse->Gradle Module ->. Eclipse used Bndtools for generating the manifest. Sample manifest as below.

Navigate to the Gradle Task view. It lists all the modules available in the workspace project. Go to Expand project > Build > Build.

These are the directives or settings in the bnd header that are added automatically to manifest.mf.

Example generated manifest.mf file

Manifest-Version: 1.0
Bnd-LastModified: 1496664974738
Bundle-ManifestVersion: 2
Bundle-Name: ktree_liferay_osgi_example
Bundle-SymbolicName: ktree_liferay_osgi_example
Bundle-Version: 1.0.0
Created-By: 1.8.0_131 (Oracle Corporation)
Import-Package: com.liferay.portal.kernel.exception;version="[7.0,8)",
 com.liferay.portal.kernel.portlet.bridges.mvc;version="[1.0,2)",com.l
 iferay.portal.kernel.service;version="[1.0,2)",com.liferay.portal.ker
 nel.theme;version="[1.0,2)",com.liferay.portal.kernel.util;version="[
 7.0,8)",com.liferay.wiki.model;version="[1.0,2)",com.liferay.wiki.ser
 vice;version="[1.1,2)",javax.portlet;version="[2.0,3)",javax.servlet,
 javax.servlet.http
Javac-Debug: on
Javac-Deprecation: off
Javac-Encoding: UTF-8
Private-Package: com.ktree.portlet,content
Provide-Capability: osgi.service;objectClass:List<String>="javax.portl
 et.Portlet",liferay.resource.bundle;bundle.symbolic.name=ktree_lifera
 y_osgi_example;resource.bundle.base.name="content.Language"
Require-Capability: osgi.extender;filter:="(&(osgi.extender=jsp.taglib
 )(uri=http://java.sun.com/portlet_2_0))",osgi.extender;filter:="(&(os
 gi.extender=jsp.taglib)(uri=http://liferay.com/tld/aui))",osgi.extend
 er;filter:="(&(osgi.extender=jsp.taglib)(uri=http://liferay.com/tld/p
 ortlet))",osgi.extender;filter:="(&(osgi.extender=jsp.taglib)(uri=htt
 p://liferay.com/tld/theme))",osgi.extender;filter:="(&(osgi.extender=
 jsp.taglib)(uri=http://liferay.com/tld/ui))",osgi.ee;filter:="(&(osgi
 .ee=JavaSE)(version=1.8))"
Service-Component: OSGI-INF/com.ktree.portlet.KtreeLiferayOsgiPortlet.
 xml
Tool: Bnd-3.2.0.201605172007

Import-Package:
com.liferay.portal.kernel.exception;version="[7.0,8)",
com.liferay.portal.kernel.portlet.bridges.mvc;version="[1.0,2)",
com.liferay.portal.kernel.service;version="[1.0,2)",
com.liferay.portal.kernel.theme;version="[1.0,2)",
com.liferay.portal.kernel.util;version="[7.0,8)",
javax.portlet;version="[2.0,3)",
javax.servlet,
javax.servlet.http,
com.liferay.wiki.model;version="[1.0,2)",
com.liferay.wiki.service;version="[1.1,2)"

Export-Package: This directive in the bnd header exports the packages to the OSGi container, which can be accessed by other modules. For example:

Export-Package: com.audit.esprocessor

If the package is already exported, we don't need to do it again — we can immediately use the option CompileOnly instead of Compile in build.gradle.

Import-Package:

  • This bnd header imports the specified package at runtime into our module.
  • Whenever we are importing a package it must be exported by some other module.
Import-Package: \
com.audit.esprocessor;version=&quot;1.0.0&quot;,\

In the import package we have two resolutions, namely:

  1. Mandatory
  2. Optional

Mandatory

  • This is the default resolution (when resolution is not specified for the import).
  • The module will be in an active state if the imported package is available in the container, otherwise the module will be in an installed state.

Optional

  • The module will go to active state without depending on import package availability.
  • It throws an exception at runtime if the imported package is not available in the container.
Import-Package: \
com.audit.esprocessor;version="1.0.0",\

Step 5: Create Liferay MVC Portlet

The final step is to create a normal Liferay MVC portlet and start using the dependencies.

As we have defined the dependencies in Eclipse, we should be able to choose our class, which we have binded.

WikiPageLocalServiceUtil.addPage(themeDisplay.getUserId(), 123, "First Wiki", 1.0, htmlContent, "", false, "html", true, "FrontPage", "", new ServiceContext());

Please refer to the source for KtreeLiferayOsgiPortlet-> addContentToWiki.

public class KtreeLiferayOsgiPortlet extends MVCPortlet {
    @ProcessAction(name="addContentToWiki")
    public void addContentToWiki(ActionRequest actionRequest,ActionResponse actionResponse) throws PortalException{
        String htmlContent = ParamUtil.getString(actionRequest, "wikiEditor");
        ThemeDisplay themeDisplay = (ThemeDisplay) actionRequest.getAttribute(WebKeys.THEME_DISPLAY);
        WikiPageLocalServiceUtil.addPage(themeDisplay.getUserId(), 123, "First Wiki", 1.0, htmlContent, "", false, "html", true, "FrontPage", "", new ServiceContext());
        System.out.println(WikiPageLocalServiceUtil.getWikiPagesCount());

    }
}

Sample Liferay OSGi Module:

  • Added the wiki api as a compile time dependency with compileOnly

  • The UI is built with aui components.

  • The UI has options to create a wiki page and also shows the list of wiki pages.

Example init.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>

<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %>
<%@ taglib uri="http://liferay.com/tld/portlet" prefix="liferay-portlet" %>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme" %>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %>

<liferay-theme:defineObjects />

<portlet:defineObjects />
<portlet:actionURL name="addContentToWiki" var="addContentToWikiUrl">
</portlet:actionURL>

Example View.jsp

<%@include file="init.jsp" %>
<aui:form action="${addContentToWikiUrl}" name="wikiform">
<h3>Enter content here</h3>
    <liferay-ui:input-editor name="wikiEditor" placeholder="Enter content here"></liferay-ui:input-editor>
    <aui:button-row>
        <aui:button type="submit"></aui:button>
    </aui:button-row>
    <br/>
    <h3><u>Wiki List</u></h3>
    <liferay-portlet:runtime portletName="com_liferay_wiki_web_portlet_WikiPortlet"/>
</aui:form>

Example Controller – KtreeLiferayOsgiPortlet.java

@Component(
    immediate = true,
    property = {
        "com.liferay.portlet.display-category=KTree",
        "com.liferay.portlet.instanceable=true",
        "javax.portlet.display-name=Ktree Liferay OSGI Portlet",
        "javax.portlet.init-param.template-path=/",
        "javax.portlet.init-param.view-template=/view.jsp",
        "javax.portlet.resource-bundle=content.Language",
        "javax.portlet.security-role-ref=power-user,user"
    },
    service = Portlet.class
)
public class KtreeLiferayOsgiPortlet extends MVCPortlet {
    @ProcessAction(name="addContentToWikiUrl")



    public void addHtmlToWikiPage(ActionRequest actionRequest,ActionResponse actionResponse) throws PortalException{


        //add logic here to add wikipage        
    }
}
Sponsors