Monday, February 8, 2016

Liferay OSGi Container - Quick Overview

What is OSGi?


In general, it is a specification that defines an architecture for modular application development. So what does it mean? The application is designed in such a way that it is having multiple extension points and the variety of the extensions can be developed during the course of the time. Those extensions developed are independent of the application and there is no dependency on each other. They live independently. Moreover, the extensions can be removed at any point of time desired.


The acronym of OSGi is Open Services Gateway Initiative. It is also known as Dynamic Module system for Java, defines two things:


  • a set of services that an OSGi container must implement
  • a contract between the container and your application.


Developing on the OSGi platform means first building your application using OSGi APIs, then deploying it in an OSGi container. From a developer's perspective, OSGi offers the following advantages:


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

OSGi in Liferay


Starting Liferay 6.2 it ships with the OSGi container.  What does that mean? Portlet applications can be designed and developed as modular applications with extension points and can be deployed on to Liferay provided OSGi container as a modules. Thus inheriting all the modular characteristics to a portlet application. Content Targeting Applications that is available in the marketplace, is built on the OSGi in 6.2. Liferay 6.2 and 7.0 (which is yet to be released) are developed with lot of extension points, providing users the flexibility to extend as per their complex use cases and needs.


In clustered environment there is no distributed OSGi container. Need to deploy on all the nodes. This may be a feature consideration for future releases. As such OSGi specification does not address this distributed containers or remote containers.


Enabling OSGi Container in Liferay 6.2
Though the OSGi container in the Liferay 6.2 is not well matured, liferay engineers provided alternative ways to make it work. As OSGi container is not enabled by default, one need to follow below steps to make it work.


  • Get the OSGi required bundles from liferay.
  • OSGi bundles are present in the data folder under <<liferay_home>>/data/osgi.
  • Create a folder called “modules” in the  folder osgi and place all the required bundles.
  • Add the following properties in the portal-ext.properties
    • module.framework.enabled=true
    • module.framework.properties.osgi.console=11311
The second property above specifies the OSGi console port number it would be running.
  • Restart the Liferay server.
Liferay is now configured with the OSGi Container and ready to be deployed modules on to it.


OSGi Console


Below are list of some important operation that can be performed in the OSGi Console


  • To access the OSGi console, in a terminal run the command
    • telnet localhost 11311
  • To see the bundles already in the container, run command “lb”. If no modules are deployed, by default it should list 16 modules.
  • The bundles will be listed  with their states (Active/Resolved/Installed)
    • Active means it is ready to be used.
    • Installed - Bundle is present but not started. We need to start it to make the status Active.
  • Starting the bundle run the command
    • “start <<component ID>>”
  • The bundle will only be started if all its dependencies are available and resolved. If not it errors.
  • To check the error, run the command
    • “diag <<component ID>>”
  • To list the Services registered in the Service Component Runtime SCR, run the command
    • “scr:list”
  • To stop the bundle run
    • “stop <<component ID>>”

The life cycle of the bundle is depicted in the following picture



Portlet Plugins as Modules


Portlet plugins can be developed as the osgi bundles and can be deployed on to the Liferay OSGi container. The portlet plugin has to conform to the OSGi specification in order to deploy it on the OSGi container. So liferay developed a process to convert the Portlet Plugins as osgi bundles. This conversion process converts the .war file .wab. .wab is called web archive bundle which can be deployed on to the OSGi Container. Liferay provides the pom file for 6.2 version for this conversion process. These wab files will be deployed to folder data/osgi/bundles not in to the default deploy folder where the war files are deployed.


Below picture shows the OSGi container that is present in the liferay server and it default services and bundles.


osgi-container-liferay-62.png


All bundles deployed on the OSGi Container will live only in the liferay osgi container.  


Some limitation of Liferay 6.2 OSGi Container


  • Can not automatically convert existing war files to osgi bundle
  • HTTPService is required. It is proxy from equinox
  • It does not support JSP’s. It supports templating languages like freemarker


All these limitations are addressed in Liferay 7.0

Developing a portlet plugin as a liferay OSGi Bundle


Let us consider a small fictitious chat application. This application contains a text box where user can send a message to other users in the liferay annotating their screen name. For now let us assume we are just displaying the list of messages that are entered in the text box, on the same screen (instead of sending it to the real user).


So the annotation of the screen name can be captured and we can write different kinds of filters, one of which would be replacing the annotation and screen name with the First and Last name of the user. We can write variety of filter on the annotated screen name.  


We can develop this whole application as a complex application with all the filters self contained in this application only. Would’nt it be nice to separate the separate the annotation interpretation logic and develop  different filters as independent plugins, so that they have their own lifecycle? OSGi makes it possible to do so. Here annotating the screen name is the extension point and the  defining different replacements for that annotated screen name are the extensions.

For this application we need three plugins
  • Portlet bundle - For the UI - as wab file
  • API - Containing the service registry and the contract (interface) - as jar file
  • Service - Implementation of the api - as jar file


TextFilter Extension


This is the interface which contains methods that any text filter extension must implement. The implementations of this interface are annotated with @Component so that OSGi will detect dynamically when a new extension is deployed on to the OSGi container.


@Component(immediate = true)
public class UserNameFilter implements TextFilter {


Referencing other services that are in the OSGi container is achieved with the annotation @Reference. In our example UserNameFilter,  we would be accessing the UserLocalService which is a liferay service. All the liferay services are bundled into OSGi container and made available to all other services that are deployed into OSGi container. Here is the code depicting the referencing of other services.


@Component(immediate = true)
public class UserNameFilter implements TextFilter {


private UserLocalService userLocalService;


@Reference
public void addUserLocalService(UserLocalService userLocalService) {
this.userLocalService = userLocalService;
}


public void removeUserLocalService(UserLocalService userLocalService) {
this.userLocalService = userLocalService;
}


Filter (Service) Registry


Registry will track all the extensions, in other words, implementations of the TextFilter interface which are annotated with @Component. It will have up to date list of the extensions when they are deployed and undeployed.


The injection point of the extensions (implementations of the TextFilter) is defined in a method with annotation @Reference so that the container will call this method whenever the extension is deployed. Here is the code snippet.


@Reference (
unbind = "removeTextFilter",
cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC)
public void addTextFilter(TextFilter textFilter) {
textFilters.add(textFilter);
}


public void removeTextFilter(TextFilter textFilter) {
textFilters.remove(textFilter);
}


Notice the unbind parameter present in the @Reference annotation. It specifies what method need to be called when extension is undeployed. This is optional parameter and if it is not provided, container will be looking for method that has a  prefix “remove” to the interface, here in our case it would be looking for removeTextFilter method.


Accessing OSGi Services from Portlet


We have developed the services and deployed them into the OSGi container. Now finally we need to access them in our portlet so that we can apply the text filter for the annotated screen name.  In Liferay 6.2, portlets are not first class citizens as a components nor services themselves in OSGi container. So they can not participate in the component framework and get the reference to the other components/services. This is like accessing OSGi services from a non OSGi context. In order to achieve this Liferay provided a custom bundle that going to  provide the reference and hide all the implementation details. This is already deployed into OSGi container. ReflectionServiceTracker is an OSGi service to track and inject other available services in the OSGi container with the portlet. We can not use a component framework here as the portlet instance is created by the portal not by OSGi container.


In order to access different services that are deployed into OSGi container, the portlet class need to have ReflectionServiceTracker started in init method and release it in destroy method.


Here is the code snippet


public class FakeChatPortlet extends FreeMarkerPortlet {


private ReflectionServiceTracker reflectionServiceTracker;


@Override
   public void init() throws PortletException {
           super.init();
           reflectionServiceTracker = new ReflectionServiceTracker(this);
   }
   
   @Override
   public void destroy() {
           super.destroy();
           if (reflectionServiceTracker != null) {
                   reflectionServiceTracker.close();
           }
   }


That is all we need to do . So we have now 2 jar files and 1 wab file. Just copy them to data/osgi/bundles and liferay will deploy them. Now you can have option to enable and disable the text filter from the OSGi console. If you stop the service, text filter will be disabled and just screen name will be printed in the list. Is you start the service, screen name will be replaced with the First Name and Last Name  

Thursday, December 4, 2014

Application Display Template - Documents and Media - Reading the Field Value from Document Type

For last couple of days I have been working on this new feature introduced in Liferay 6.2 Application Display Templates (aka ADT).

ADT Provides adding custom display settings to our portlets. Initially when liferay 6.2 was released ADT can only be applied to a specified list of  OOTB portlets

  • Asset Publisher
  • Blogs
  • Categories Navigation
  • Media Gallery
  • Site Map
  • Tags Navigation
  • Wiki
But later the scope is extended to have ADT's for custom portlets as well. 

That being said let me focus of the heading of this blog.

Document Type holds the meta data of the document and can be created in the liferay portal with different scopes (Global, Portal, Site). Document Type can be defined to have different fields with different data types.  We can also restrict a folder to have a specified document type. 

There is no direct API for retrieving the document type field values for a given document, for a given document.  Thus it becomes more complicated to retrieve those in the free marker template.

  1. By default serviceLocator variable is restricted in portal.properties we need to enable serviceLocator in portal-ext.properties, by adding below line,  so that we can access different liferay servcies. 
    • freemarker.engine.restricted.variables=
  2. Retrieve DLFileEntryLocalService with service Locator
    • <#assign dlFileEntryService = serviceLocator.findService("com.liferay.portlet.documentlibrary.service.DLFileEntryLocalService")>
  3. Retrieve the DLFileEntry Object for a given FileEntry Object
    • <#assign dlFileEntry = dlFileEntryService.getFileEntry(curFileEntry.getFileEntryId() )>
  4. From the DLFileEntry get all the fields available as Map.
    • <#assign fieldsMap = dlFileEntry.getFieldsMap(dlFileEntry.getFileVersion().getFileVersionId()) >
  5. Now Iterate through the Map and retrieve the fields available in the document type of a given document.
    <#list fieldsMap?keys as structureKey>

    <#list fieldsMap[structureKey].iterator() as field>
    <#assign imgList = imgList + {field.getValue():curFileEntry}> 
    </#list>
    </#list>

That is all we need to do. 


Here are some useful links that might be helpful while developing the ADT's 

http://www.liferay.com/web/james.falkner/blog/-/blogs/dumping-adt-wcm-template-variables?_33_redirect=http%3A%2F%2Fwww.liferay.com%2Fweb%2Fjames.falkner%2Fblog%3Fp_p_id%3D33%26p_p_lifecycle%3D0%26p_p_state%3Dnormal%26p_p_mode%3Dview%26p_p_col_id%3Dcolumn-2%26p_p_col_count%3D1
https://www.liferay.com/community/forums/-/message_boards/message/44019133
https://www.liferay.com/es/community/forums/-/message_boards/message/44197544
https://issues.liferay.com/browse/LPS-50931
http://www.liferay.com/web/eduardo.garcia/blog/-/blogs/new-ways-of-customization-with-application-display-templates-part-i-
http://www.liferay.com/web/eduardo.garcia/blog/-/blogs/new-ways-of-customization-with-application-display-templates-part-ii-
 

Kaleo Workflow Limitations and Considerations.

Limitations w.r.t liferay 6.1.20

  • Can not have multiple terminal (end) states.
  • Can not send notifications in the terminal state for the roles though it can send the notifications to users specified.
  • There is no scripted assignment for notifications separately. In other words, <recipients> tag in the <notifications> does not have a provision for scripted assignment. 


Things to be considered while developing the Kaleo workflows.


  • When ever there is a script error, workflow engine does not give/throw the exception and swallows the exception silently.
  • The only way to check whether the script executes perfectly or not is not copy the script and run that script in the Control Panel --> Server Administration --> Scripts console and debug there.
  • Scripted assignment is present at <Task> level. If the notification does not have < recipients> tag, then the notification uses the assignment tag specified at the task level. Thus, task assignment and notifications are sent to the same set of users.





Monday, August 4, 2014

Override annotation compilation problem - Liferay 6.1 Plugins SDK

You have classes implementing interface methods, which in Java 1.6 can be annotated with @Override; however, in Java 1.5, @override could only be applied to methods overriding a superclass method.

So while using liferay 6.1 plugins SDK, if you use @override annotations for implementing interface methods, though the server JDK is at 1.6, it still complains, resulting in not compiling the class with that annotation. Here is the example:

When creating a model listener hook, you need to implement all of the methods present in the ModelListener interface. If the implemented class has annotation "override", while building using the ant, it gives compilation error. 

Temporary fix

Remove @override annotation and build the project using ant.

Permanent Fix

To solve this issue the compiler compliance level should be set to 1.6. As we are building from ant, we have to set the compiler compliance level of ant. This can be set in build.properties file present in the plugins SDK root directory. All you need to do is change the following properties to 1.6

ant.build.javac.source=1.6

ant.build.javac.target=1.6

Thursday, November 7, 2013

WAR file built Using Liferay Plugins SDK contains java source files

The standard Liferay SDK build process includes all java sources to the deployment units (war file).

As Liferay is Open Source, it makes sense to include the code also in the war, if someone just download the plugin war and installs it. This holds good for the plugins developed to be shared among the community. But it is not acceptable while building it for any enterprise project.

It is possible to override the war target in individual build.xml of the portlet or you could just replace the target with your own in the build-common-plugin.xml so it applies to all portlets in your SDK. Below is the snippet.

<target name="war" depends="compile">
<mkdir dir="${sdk.dir}/dist" />

<if>
<available file="tmp" />
<then>
<property name="docroot.dir" value="tmp" />
</then>
<else>
<property name="docroot.dir" value="docroot" />
</else>
</if>

<delete file="${plugin.file}" />

<antcall target="clean-portal-dependencies" />

<if>
<available file="docroot/WEB-INF/liferay-hook.xml.processed" />
<then>
<property name="liferay-hook.xml.excludes" value="WEB-INF/liferay-hook.xml*" />
</then>
<else>
<property name="liferay-hook.xml.excludes" value="" />
</else>
</if>

<if>
<contains string="${app.server.dir}" substring="glassfish" />
<then>
<zip
basedir="${docroot.dir}"
destfile="${plugin.file}"
excludes="**/META-INF/context.xml,${liferay-hook.xml.excludes},${plugins.war.excludes}, WEB-INF/src/**"
>
<zipfileset
dir="${docroot.dir}"
fullpath="WEB-INF/liferay-hook.xml"
includes="WEB-INF/liferay-hook.xml.processed"
/>
</zip>
</then>
<else>
<zip
basedir="${docroot.dir}"
destfile="${plugin.file}"
excludes="${liferay-hook.xml.excludes},${plugins.war.excludes}, WEB-INF/src/**"
>
<zipfileset
dir="${docroot.dir}"
fullpath="WEB-INF/liferay-hook.xml"
includes="WEB-INF/liferay-hook.xml.processed"
/>
</zip>
</else>
</if>

<if>
<and>
<equals arg1="${plugins.src.zip.enabled}" arg2="true" />
</and>
<then>
<zip destfile="${plugin.src.file}">
<zipfileset
dir="${docroot.dir}"
excludes="${liferay-hook.xml.excludes}"
prefix="${plugin.name}-src-${plugin.full.version}"
/>
<zipfileset
dir="${docroot.dir}"
fullpath="${plugin.name}-src-${plugin.full.version}/WEB-INF/liferay-hook.xml"
includes="WEB-INF/liferay-hook.xml.processed"
/>
</zip>
</then>
</if>
</target>

Adding Documents to liferay Document Library through API

While adding documents to documents library there are two ways of doing it using the liferay API,  DLFileEntryLocalServiceUtil or DLAppLocalServiceUtil


Difference between DLFileEntryLocalServiceUtil and DLAppLocalServiceUtil

DLFileEntry services and DLFolderEntry services are specifically for storing file and folder entries in liferay's database and are totally unaware of the new repository concept introduced in 6.1. The user-guide & this wiki explains how to add a new repository.

Where as DLApp (DLAppService & DLAppLocalService) services take into account these things i.e. to say that they take care of syncing documents between liferay database and other repositories, and not just store entries in Liferay database.

Adding documents to Documents Library

1. Get the File object (using input stream) of the file which is being inserting to the document library.

2. Get the FileInputStream 

InputStream inputStream = new FileInputStream( file );

3. Get the ThemeDisplay from the request. This is used for retrieving the user id and group id while adding the documents which is depicted in the further steps down.

ThemeDisplay themeDisplay = ( ThemeDisplay ) actionRequest.getAttribute( WebKeys.THEME_DISPLAY );

4. Get the Service Context object

ServiceContext serviceContext = ServiceContextFactory.getInstance( actionRequest );

5. Get the access folder in which the document would be placed.

Folder folder = DLAppLocalServiceUtil.getFolder( themeDisplay.getScopeGroupId(), 0, "<<Name of the folder>>" );

Here the first argument is the repository ID,  If the repository is a default Liferay repository, the repositoryId is the groupId or scopeGroupId. Otherwise, the repositoryId will correspond to values obtained from RepositoryLocalServiceUtil.

6. Now finally the inserting to the document library using DLAppLocalServiceUtil

FileEntry fileEntry = DLAppLocalServiceUtil.addFileEntry( themeDisplay.getUserId(), folder.getRepositoryId(), folder.getFolderId(), fileName,
contentType, fileName, fileName, "changeLog", inputStream, file.length(), serviceContext );


That is all we need to do, document is now inserted in the document library.

Monday, November 4, 2013

Defining custom Velocity Variables for Liferay Themes

There are number of predefined variables that can be accessed from with in the liferay theme. For elaborated list of those, below is the url

http://www.liferay.com/community/wiki/-/wiki/Main/Access+Objects+from+Velocity?_36_pageResourcePrimKey=2905717

Apart from those variables, if at all we end up needing a custom variable, which define a custom functionality/value as per the specific business need, we can define them in two places.

1. init_custom.vm. 

init.vm file, which is present in the liferay theme, contains all the liferay defined velocity variables. As a best practice, all the custom variables should be defined in the init_custom.vm file. 
This file allows you to override and define new Velocity variables.

#set($background_url = $theme_display.getThemeSetting('background-url'))

In the above example we are reading the background-url value provided by the user, while configuring the themes.

2. Setting the variables in request scope under attribute WebKeys.VM_VARIABLES

Create a hook, either Service Pre action or Login Post Action hook and inside your extended pre or post Action class, create a Map object with the key and value of the vm variables you want to insert, and then set that into the attribute under the key WebKeys.VM_VARIABLES. The variables added to the map are directly accessible in the Theme. This method provide more flexibility than the earlier one as there are no restrictions for defining the variables in this method.

Here is the code snippet for the same


Map< String, Object > vmVariables = ( Map< String, Object > ) request.getAttribute( WebKeys.VM_VARIABLES );

if ( vmVariables == null ) {
         vmVariables = new HashMap< String, Object >();
}
vmVariables.put("someVariable", "someValue");


request.setAttribute( WebKeys.VM_VARIABLES, vmVariables );

Now the variable added to the Map is directly accessible from with in the velocity template file in the theme.

For example it can be directly accessible from portal_normal.vm as below

<a id="siteLogo" class="logo $logo_css_class" href="$someVariable" title="#language("go-to") $site_name"/>