Developing Liferay Portlets With ZK

    I have written here before about ZK, the AJAX library for Java. One of my job requirements recently has been to find an alternative to our in-house developed Intranet, which is long since unmaintainable because of lack of documentation and employee turnover. My solution? Liferay Portal Server. Liferay Portlets are simple enough to develop using traditional Java servlet methodologies, but when you get into AJAX the complexity level increased by an order of magnitude. So, the purpose of this article is to allow you to get started writing portlets using ZK in a jiffie!!


The Development Environment



    My development environment is Eclipse Ganymede. I have installed the ZK Studio plug-in, the Tigris Subclipse Subversion plug-in, and the Web Tools Platform. Once you have this, you have a good starting point. You can add other things like Java Persistence, Hibernate, JSF, and Struts; but the basics are enough to write simple portlets.

The Test Environment


    I am developing for Liferay version 5.2.2 (Augustine). I downloaded the Liferay/Tomcat6 bundle and extracted it into my home directory (Ubuntu/Linux). Once it was extracted, DO NOT start the server immediately. You have to make some configuration changes before you launch the service the first time. For me, these changes included setting up Active Directory authentication with our Windows domain controllers, changing from the embedded HSQLDB database to PostgreSQL, and setting some more sane defaults for the WebDAV file access.

    The aforementioned changes are done by creating a portal-ext.properties file. In the folder which was created from extracting the Liferay/Tomcat6 bundle, look for the <Liferay>/tomcat-6.x.x/webapps/ROOT/WEB-INF/classes folder. Create a new text file in this folder called "portal-ext.properties". Below is a sanitized version of my portal-ext.properties file.


#
# Settings which allow our users to authenticate with their Windows username & password.
#
ldap.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
ldap.base.provider.url=ldap://192.168.100.18:389
ldap.base.dn=dc=example,dc=com
ldap.security.principal=admin@example.com
ldap.security.credentials=myadminpassword
ldap.auth.enabled=true
ldap.auth.required=false
ldap.auth.method=bind
ldap.auth.password.encryption.algorithm=
ldap.auth.password.encryption.algorithm.types=MD5,SHA
# ldap.auth.search.filter=(cn=@screen_name@)
ldap.auth.search.filter=(samaccountname=@screen_name@)
ldap.user.mappings=screenName=sAMAccountName\npassword=userPassword\nemailAddress=mail\nfirstName=givenName\nlastName=sn\njobTitle=title\ngroup=memberOf\nfullName=cn
# ldap.group.mappings=groupName=cn\ndescription=description\nuser=uniqueMember
ldap.group.mappings=groupName=cn\ndescription=description\nuser=member
ldap.import.enabled=true
ldap.import.on.startup=true
ldap.import.interval=10
ldap.import.user.search.filter=(&(!(userAccountControl:1.2.840.113556.1.4.803:=2))(objectCategory=Person)(sAMAccountName=*))
ldap.import.group.search.filter=(objectCategory=Group)
ldap.import.method=user
# ldap.import.method=group
ldap.export.enabled=false
ldap.password.policy.enabled=false
auth.forward.by.last.path=false
terms.of.use.required=false
company.default.web.id=example.com
company.login.prepopulate.domain=false
company.security.auth.type=screenName

#
# Change the default database from HSQLDB to PostgreSQL or others (Remember to put the proper JDBC jar file into the tomcat common library directory)
#
jdbc.default.driverClassName=org.postgresql.Driver
jdbc.default.url=jdbc:postgresql://localhost:5432/lportal
jdbc.default.username=lportal
jdbc.default.password=lportal

#
# These settings allow Liferay to build the database tables for you using Hibernate's hbm2ddl facility
#
schema.run.enabled=true
schema.run.minimal=true

#
# Various settings which you can look into by downloading the Liferay source, but probably are very specific to our environment.
#
layout.user.public.layouts.modifiable=true
users.reminder.queries.enabled=false
default.landing.page.path=/web/guest/login
com.liferay.portal.upload.UploadServletRequestImpl.max.size=10485760
dl.file.max.size=10240000
dl.file.extensions=*
layout.template.cache.enabled=false
velocity.engine.resource.manager.cache.enabled=false

#
# Disables "Updates Available" messages in Liferay.
#
plugin.notifications.enabled=false


Writing The Portlet


    Basically, writing a portlet in ZK is no more difficult that writing any other ZK applications (which is to say VERY EASY). The only difference is the configuration XML files which have to be added into the application structure. Typically, you will have a file structure for your web application like the following:



    Let's talk about those extra XML files shown. The first one of concern is the portlet.xml file. Below is an extract of a portlet.xml file:


<?xml version="1.0" encoding="UTF-8"?>
<portlet-app version="1.0"
xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd">
<portlet>
<description xml:lang="EN">Summary of sales by branch for a given date</description>
<portlet-name>SalesSummary</portlet-name>
<display-name xml:lang="EN">SalesSummary</display-name>
<portlet-class>org.zkoss.zk.ui.http.DHtmlLayoutPortlet</portlet-class>
<expiration-cache>0</expiration-cache>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>view</portlet-mode>
</supports>
<supported-locale>en</supported-locale>
<portlet-info>
<title>Sales Summary</title>
<short-title>SalesSummary</short-title>
<keywords>Sales Summary</keywords>
</portlet-info>
<portlet-preferences>
<preference>
<name>zk_page</name>
<value>/sales_summary_view.zul</value>
</preference>
</portlet-preferences>
<security-role-ref>
<role-name>power-user</role-name>
</security-role-ref>
<security-role-ref>
<role-name>user</role-name>
</security-role-ref>
<security-role-ref>
<role-name>administrator</role-name>
</security-role-ref>
</portlet>
</portlet-app>

    You could easily just copy my portlet.xml file and modify a few things and it would work. You can even create multiple portlets in a single WAR file by replicating the "portlet" node with more portlet instances. The ZUL file listed in the XML settings is the file which will be displayed when the portlet is viewed in Liferay.

    The next XML file to look at is the liferay-portlet.xml file. This file is demonstrated below:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE liferay-portlet-app PUBLIC "-//Liferay//DTD Portlet Application 5.2.0//EN" "http://www.liferay.com/dtd/liferay-portlet-app_5_2_0.dtd">
<liferay-portlet-app>
<portlet>
<portlet-name>SalesSummary</portlet-name>
</portlet>
<portlet>
<portlet-name>InvoiceSummary</portlet-name>
</portlet>
<portlet>
<portlet-name>ShipSummary</portlet-name>
</portlet>
<role-mapper>
<role-name>user</role-name>
<role-link>User</role-link>
</role-mapper>
<role-mapper>
<role-name>power-user</role-name>
<role-link>Power User</role-link>
</role-mapper>
<role-mapper>
<role-name>administrator</role-name>
<role-link>Administrator</role-link>
</role-mapper>
</liferay-portlet-app>

    This file is very straight forward, it's mostly a rehash of the portlet.xml file with less details. So, we'll quickly move on to the liferay-display.xml file. This file allows you to assign your portlets to "Categories" within Liferay's application hierarchy. Very useful when you have a lot of portlets. An example is shown below:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE display PUBLIC "-//Liferay//DTD DISPLAY 2.0.0//EN" "http://www.liferay.com/dtd/liferay-display_2_0_0.dtd">

<display>
<category name="My Company">
<portlet id="SalesSummary" />
<portlet id="InvoiceSummary" />
<portlet id="ShipSummary" />
</category>
</display>

    Last, but not least, is the liferay-plugin-package.xml file. This file tells Liferay how to deploy the portlets within it's environment.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plugin-package PUBLIC "-//Liferay//DTD Plugin Package 4.3.6//EN" "http://www.liferay.com/dtd/liferay-plugin-package_4_3_0.dtd">
<plugin-package>
<name>My Company Portlets</name>
<module-id>/com/mycompany/portlets/war</module-id>
<types>
<type>portlet</type>
</types>
<short-description>
My Company's Plug-Ins
</short-description>
<change-log>
Initial Deployment
</change-log>

<author>Deven Phillips</author>
<licenses>
<license osi-approved="true">GPL</license>
</licenses>
<liferay-versions>
<liferay-version>5.1.1+</liferay-version>
<liferay-version>5.2.1+</liferay-version>
</liferay-versions>
</plugin-package>

    Just be sure that you create a new <portlet> node for each portlet in the portlet.xml file and you should get the hang of this fairly quickly.

Writing The ZK Application


    There are really only two things to keep in mind when using ZK inside of Liferay. The first, Liferay abstracts ZK to a certain extent, so you MUST use a bounding Window to be the base of all of your portlets. This will ensure that components will not have a namespace clash. I usually use something like this:
<window border="none" mode="embedded"/>

It will not even show up as visible, but it is enough to prevent two ZK portlets on the same page from interfering with each other.

    The second thing to remember is that you cannot use
execution.sendRedirect()
. If you need to load a second ZUL page, use
Executions.createComponents()
and replace the existing components with your new components. If you try to redirect the user to another ZUL page, it will redirecto to a non-existant generated URL.

    Liferay implements several features designed to speed up application processing and serving content. For most applications these features work very well, but in the case of the dynamically generated JavaScript from ZK, this all causes problems with how the ZK applications. I found THIS article from the ZK forum which helped me. You create a new properties file which you name liferay-plugin-package.properties and in the file put a line which reads:


speed-filters-enabled=false

Save that file in your WEB-INF for your new portlet WAR file and you are good to go!

    It took me about 2 weeks of constant searching to figure out these tips, so I hope that they are useful to a lot of people. If you have questions, feel free to post and I will try to answer them to the best of my abilities.

Cheers!

Comments

Unknown said…
Deven,

Thanks for the article. I'm deploying Liferay 5.2.2 and use Eclipse as my dev environment too. Your article is a big help in getting a portlet dev environment going for ZK! We're also authenticating against AD -- so your work is directly relevant to my my team's work

I'd love to stay in touch with your LR developments.

-Chris
Hi,

Could you attach the sample portlet of this article.

Regards,
Raja Nagendra Kumar,
C.T.O
www.tejasoft.com
Luis Bertel said…
Hi Deven,

it would be possible to have the source code example.

tank

Luis Bertel
Anonymous said…
Hey Deven et al,

I currently looking at what UI frameworks to use for developing Liferay portlets. I'm looking at Icefaces but your article has lead me to check out ZK.

In particular ZK's support for its

1)Markup and scripting Languages
2)template-based looks and customizable behavior

Does any one have any comments comparing these two frameworks in developing LR portlets (inner portlet communication, AJAX server side push etc)

Cheers
Al
Unknown said…
Hi Deven,

I'm struggeling with zk events in liferay since days now.

Did you manage to handle them correctly. Any tricks?

Thanks

Andreas
Anonymous said…
Hi,

Thanks for the article. It took me 90% of the way.

Eventually this link provided teh last mile: http://fabianmaass.de/?p=12
-jensse
Anonymous said…
I've followed your article and all worked correctly. Really thanks, very useful!

I've only a problem: when i add
a ZK portlet to the portal the banner of Liferay portal slightly shrinks (a small white border appears at left and right). Any ideas?
Unknown said…
I Have a problem displaying a ZK portlet in Liferay..I have created a zk portlet using the instructions in this blog.I can see my portlet in the liferay page but as soon as my But unfortunately as ZK portlet loads on the page my entire page becomes non-responsive.I NEED IMMEDIATE HELP .. :((

Popular Posts