Polling the Liferay Portal

>> Sunday, July 3, 2011

On today's web sites, it is often desirable to have a page refresh it's content through AJAX calls. How can we achieve this in a Liferay portlet? Well, it turns out that the Liferay folks have already solved this problem and provide all the plumbing required to do it with minimal effort and overhead. In fact, the Liferay Chat portlet relies on this PollerService extensively. In the following, I describe how to tap into this bit of infrastructure through a working (albeit simple) example. The source code for this example Liferay plugin is available in our Maven repository.

In a nutshell, here is what you need to do:

1) Specify a Poller Processor Class

First, we specify the following in our plugin's liferay-portlet.xml file:

      <portlet>
     <portlet-name>opnworks_poller_example</portlet-name>
     <icon>/icon.png</icon>
     <poller-processor-class>com.opnworks.portal.example.poller.ExamplePollerProcessor</poller-processor-class>
     <instanceable>false</instanceable> 
     <private-request-attributes>false</private-request-attributes>
     <private-session-attributes>false</private-session-attributes>
     <header-portlet-css>/css/main.css</header-portlet-css>
     <footer-portlet-javascript>/js/main.js</footer-portlet-javascript>
</portlet>

The ExamplePollerProcessor class must implement the com.liferay.portal.kernel.poller.PollerProcessor interface. For the example, we simply extend the following abstract class: com.liferay.portal.kernel.poller.BasePollerProcessor.

In the above, we set "instanceable" to false which makes our life easier and is generally preferable since we do not necessarily want to have n portlets in the same page polling our portal service.

2) Implement the PollerProcessor

Our PollerProcessor implementation is quite simple: each poll request writes to the log and returns the current time in a JSON object. This is what we do here:

 package com.opnworks.portal.example.poller;
import java.util.Date;
import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.poller.BasePollerProcessor;
import com.liferay.portal.kernel.poller.PollerRequest;
import com.liferay.portal.kernel.poller.PollerResponse;
public class ExamplePollerProcessor extends BasePollerProcessor {
private static Log _log = LogFactoryUtil.getLog(ExamplePollerProcessor.class);
@Override
protected void doReceive(PollerRequest pollerRequest,
          PollerResponse pollerResponse) throws Exception {
     _log.info("doReceive invoked.\n\tRequest: "
               + pollerRequest +
               "\n\tResponse: " + pollerResponse);
     JSONObject responseJSON = JSONFactoryUtil.createJSONObject();
     responseJSON.put("time", (new Date()).toString());
     pollerResponse.setParameter("content", responseJSON);
}
@Override
protected void doSend(PollerRequest pollerRequest) throws Exception {
     String status = getString(pollerRequest, "status");
     _log.info("doSend invoked. \n\tRequest: " + pollerRequest +
               "\n\tStatus: " + status);
}
}

3) Provide some JavaScript Glue

The Javascript code included in the example portlet is the following:

 AUI().use(
   'aui-base',
   'aui-delayed-task',
   'liferay-poller',
   function(A) {
        Liferay.namespace('PollEx');
        Liferay.PollEx.Manager = {
                  init: function(delay, encryptedUserId, portletId, containerId) {
                       var instance = this;
                       instance._delay = delay;
                       instance._portletId = portletId;
                       instance._pollExContainer = A.one('#' + containerId);
                       //Liferay.Poller.setEncryptedUserId(encryptedUserId);
                       console.log("encryptedUserId: " + encryptedUserId);
                       Liferay.Poller.init({
                            encryptedUserId : encryptedUserId,
                            supportsComet : false
                       })
                       instance._startPolling();
                       //alert(instance._pollExContainer);
                  },
                  _startPolling: function() {
                       var instance = this;
                       var task = new A.DelayedTask(instance.send, instance);
                       console.log("_delay: " + instance._delay + ", _portletId:" + instance._portletId + ", _pollExContainer: " + instance._pollExContainer);
                       //Liferay.Poller.setEncryptedUserId('vWdO6/SXDNI='); Q8Nczjmq7zU=
                       task.delay(instance._delay, null, null, [{
                                 portletId: instance._portletId
                                 }]
                            );                        
                       Liferay.Poller.addListener(instance._portletId, instance._onPollerUpdate, instance);
                       Liferay.on(
                            'sessionExpired',
                            function(event) {
                                 Liferay.Poller.removeListener(instance._portletId);
                                 //instance._pollExContainer.hide();
                            }
                       );
                  },
                  send: function(options, id) {
                       var instance = this;
                       console.log("options:" + options + " id: " + id);
                       Liferay.Poller.submitRequest(instance._portletId, options, id);
                  },
                  _onPollerUpdate: function(response, chunkId) {
                       var instance = this;
                       instance._pollExContainer.text(response.content.time);
                       instance.send(
                                 {
                                      status: 'OK'
                                 }
                            );
                  },
             };    
        A.augment(Liferay.PollEx.Manager, A.Attribute, true);
   }
);    

The critical lines of code are where we create and start a "DelayedTask" (new A.DelayedTask) and where we register a listener (Liferay.Poller.addListener...).

Essentially, we are setting up a task that fires every n ms (in our case, 10000) and a listener that gets called back when the response comes in. In addition, every callback fires a "send" to the server with a status.

4) Put a View on Things

Everything is in place but we still need a view. The view.jsp file contains the following:

 <%@page import="com.liferay.portal.kernel.util.GetterUtil"%>
<%@page import="com.liferay.portal.model.User"%>
<%@page import="com.liferay.portal.service.UserLocalServiceUtil"%>
<%@ page import="com.liferay.util.Encryptor" %>
<%@ include file="/init.jsp" %>
<%
User theUser = user;
//if (user == null) {
// Get default user
theUser = UserLocalServiceUtil.getDefaultUser(themeDisplay.getCompanyId());
//}
String encryptedUserId = Encryptor.encrypt(company.getKeyObj(), "" + theUser.getUserId());
%>
<h2>This is the OpnWorks Poller Example</h2>
<p id="<portlet:namespace />pollExContainer">
<!-- PLACEHOLDER -->
</p>
<aui:script use="aui-base">
AUI().ready(function(A) {
          Liferay.PollEx.Manager.init(20000, '<%=encryptedUserId %>', '<%= portletDisplay.getRootPortletId() %>',
               '<portlet:namespace />pollExContainer');
     });
</aui:script>

Pretty simple no? We call the "setIds" method once with the appropriate parameters and we have a periodically refreshed timestamp displayed in our portlet in the element with id pollExContainer.

5) One Gotcha to Top it Off

Despite my attempts, I could not get the poller service to work for a Guest (unauthenticated) user. This is possibly by design and fits the need of the Chat portlet but it would be very useful to be able to invoke the poller service for guest users. If anybody has suggestions or a solution please feel free to comment.

Meanwhile, have fun with your portlets...

3 commentaires:

Anonymous,  09 January, 2014 15:38  

the link of the maven repository doesn't work

Laurent Gauthier 09 January, 2014 19:55  

Sorry about that. The link is now fixed.

Anonymous,  23 February, 2015 11:20  

Could you explain what actualy does ExamplePollerProcessor? Is it works with client request/response only? How to send something from serverside?
Thanks.
Dmitry.

Post a Comment

  © Blogger template Webnolia by Ourblogtemplates.com 2009

Back to TOP