© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
A. Prakash, S. I. BashaHands- On Liferay DXPhttps://doi.org/10.1007/978-1-4842-8563-3_4

4. Advanced Liferay Concepts

Apoorva Prakash1   and Shaik Inthiyaz Basha2
(1)
BANGALORE, India
(2)
Nellore, AP, India
 

This chapter focuses on a few of the essential and advanced concepts of Liferay DXP. You learn how to set communication between two portlets using inter-portlet communication. There are several approaches to IPC, including IPC via events, an approach that is commonly used in the industry because of its versatility. Another critical concept—the message bus—is also covered in this chapter so that you understand how messaging works in Liferay for background processes. The most implemented form of the message bus is the asynchronous message bus, mainly used for sending bulk mail or batch-processing tasks. Other important topics covered in this chapter include scheduler implementation and scheduled tasks managed by them, such as periodic reports, and so on.

Inter-Portlet Communication

Inter-portlet communication, or IPC, enables communication between two portlets when are deployed on the same instance of the Liferay DXP server. These portlets can be on the same page or different pages, and there are different approaches to IPC depending on the scenario. This communication entails passing a message and some data from Portlet A to Portlet B. The complete concept of inter-portlet communication is based on two kinds of portlets—a sender and a receiver portlet. They are normal portlets but include extra definitions in their metadata to identify their behavior. The portlet invoking the IPC process is referred to as the sender portlet, whereas the one receiving communication is referred to as the receiver portlet. Sender and receiver portlets are also often referred to as producer portlets and consumer portlets, respectively.

The first Portlet Specification (1.0, JSR 168) had minimal capability for IPC. The only way to achieve IPC was portlet session-based; essentially, just setting attributes in a session’s portlet makes data accessible to the other portlets. Then came the Portlet Specification 2.0 (JSR 286). It revolutionized the IPC process. It came up with many improved approaches to achieve IPC, such as events, and so on.

Note

Liferay portals support all types of inter-portlet communication (IPC) enlisted in Portlet Specification 1.0 (JSR 168) and Portlet Specification 2.0 (JSR 286).

There are five ways of achieving IPC; let’s review them individually.

IPC via Public Render Parameters

Public Render Parameters (PRPs) are one of the most straightforward mechanisms to achieve inter-portlet communication. These portlets may be on different pages or the same page. This approach to inter-portlet communication works on the render parameters, which multiple portlets can access. This approach works on portlets added to the same page by default Liferay configurations; however, this behavior can be modified using scopes.

To use PRPs, you need to define specific portlet parameters. This portlet acts as the producer portlet. This specified parameter goes to another portlet, which has a specific value. As the producer portlet’s parameter is accessible by another portlet, the data being carried by this parameter becomes accessible to another portlet. The portlet accessing the parameter to access data is the consumer portlet in this scenario. To make a portlet a consumer, you specify it in the portlet config, similar to how you defined the producer portlet.

Let’s look at this more closely with an example. Consider these two portlets to achieve inter-portlet communication, as shown in Figure 4-1.

A screenshot of portlets under modules in the project explorer folder. In a folder name modules, apress I P C receiver apress I P C sender, and apress M V C is present.

Figure 4-1

Two portlets to achieve inter-portlet communication

Select apressIPCSender and apressIPCReceiver; you will work with these portlets to achieve all varieties of IPC. (I do not discuss portlet creation in detail here, as it’s covered in Chapter 3.)

In the apressIPCSender portlet, use the code shown in Listing 4-1 to achieve the IPC via the PRP.
<%@ 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 />
<div style="padding: 15px">
        <h3><liferay-ui:message key="apressipcsender.caption"/></h3>
        <portlet:actionURL name="passMessage" var="passMessageURL" />
        <aui:form name="ipcForm" action="${passMessageURL}" method="post">
                <aui:input name="inputMessage" type="text" label=" Pass Message as Parameter">
             </aui:input>
                <aui:button type="submit"></aui:button>
        </aui:form>
</div>
Listing 4-1

View.jsp of apressIPCSender Portlet for PRP

In the code shown in Listing 4-2, you can see the highlighted property, which is crucial to performing the PRP IPC. This property should be the same in the sender and receiver portlets.
package com.apress.handsonliferay.ipc.sender.portlet;
import com.apress.handsonliferay.ipc.sender.constants.ApressIPCSenderPortletKeys;
import com.liferay.portal.kernel.portlet.bridges.mvc.MVCPortlet;
import com.liferay.portal.kernel.util.ParamUtil;
import java.io.IOException;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.Portlet;
import javax.portlet.PortletException;
import javax.portlet.PortletSession;
import javax.portlet.ProcessAction;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.xml.namespace.QName;
import org.osgi.service.component.annotations.Component;
/**
 * @author Apoorva_Inthiyaz
 */
@Component(
        immediate = true,
        property = {
                "com.liferay.portlet.display-category=category.ipc",
                "com.liferay.portlet.header-portlet-css=/css/main.css",
                "com.liferay.portlet.instanceable=true",
                "com.liferay.portlet.private-session-attributes=false",
                "javax.portlet.display-name=ApressIPCSender",
                "javax.portlet.init-param.template-path=/",
                "javax.portlet.init-param.view-template=/view.jsp",
                "javax.portlet.name=" + ApressIPCSenderPortletKeys.APRESSIPCSENDER,
                "javax.portlet.resource-bundle=content.Language",
                "javax.portlet.security-role-ref=power-user,user",
                "javax.portlet.supported-public-render-parameter=inputMessage"
                },
        service = Portlet.class
)
public class ApressIPCSenderPortlet extends MVCPortlet {
        public void passMessage(ActionRequest actionRequest, ActionResponse actionResponse)
                        throws IOException, PortletException {
                // TODO Auto-generated method stub
                String passMessageVal = ParamUtil.getString(actionRequest, "inputMessage","");
                actionResponse.getRenderParameters().setValue("inputMessage", passMessageVal);
        }
}
Listing 4-2

ApressIPCSenderPortlet Controller Class for PRP

In the apressIPCReceiver portlet, use the code shown in Listings 4-3 and 4-4 to achieve IPC via the PRP.
<%@ include file="/init.jsp" %>
<h5><liferay-ui:message key="apressipcreceiver.caption"/></h5>
        <h2>Received Message from Public Render Parameter :<div style="color:blue">  ${recievedMessage}</div></h2>
Listing 4-3

View.jsp of the apressIPCReceiver Portlet for PRP

package com.apress.handsonliferay.ipc.receiver.portlet;
import com.apress.handsonliferay.ipc.receiver.constants.ApressIPCReceiverPortletKeys;
import com.liferay.portal.kernel.portlet.bridges.mvc.MVCPortlet;
import com.liferay.portal.kernel.util.ParamUtil;
import java.io.IOException;
import javax.portlet.Event;
import javax.portlet.EventRequest;
import javax.portlet.EventResponse;
import javax.portlet.Portlet;
import javax.portlet.PortletException;
import javax.portlet.PortletSession;
import javax.portlet.ProcessEvent;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.osgi.service.component.annotations.Component;
/**
 * @author Apoorva_Inthiyaz
 */
@Component(
        immediate = true,
        property = {
                "com.liferay.portlet.display-category=category.ipc",
                "com.liferay.portlet.header-portlet-css=/css/main.css",
                "com.liferay.portlet.instanceable=true",
                "com.liferay.portlet.private-session-attributes=false",
                "javax.portlet.display-name=ApressIPCReceiver",
                "javax.portlet.init-param.template-path=/",
                "javax.portlet.init-param.view-template=/view.jsp",
                "javax.portlet.name=" + ApressIPCReceiverPortletKeys.APRESSIPCRECEIVER,
                "javax.portlet.resource-bundle=content.Language",
"javax.portlet.security-role-ref=power-user,user",
                "javax.portlet.supported-public-render-parameter=inputMessage",
                "javax.portlet.supported-processing-event=produceMessage;http://inthiyaz.com"
        },
        service = Portlet.class
)
public class ApressIPCReceiverPortlet extends MVCPortlet {
        @Override
        public void doView(RenderRequest renderRequest, RenderResponse renderResponse)
                        throws IOException, PortletException {
                // TODO Auto-generated method stub
                String passMessageVal =  ParamUtil.getString(renderRequest, "inputMessage","");
                System.out.println("Received Message "+passMessageVal);
                renderRequest.setAttribute("recievedMessage", passMessageVal);
                PortletSession portletSession = renderRequest.getPortletSession();
                renderRequest.setAttribute("recievedSessionMessage",
                                (String)portletSession.getAttribute("Liferay_Shared_Session_Para",PortletSession.APPLICATION_SCOPE));
                super.doView(renderRequest, renderResponse);
                }
}
Listing 4-4

The ApressIPCReceiverPortlet Controller Class for PRP

As you can see, javax.portlet.supported-public-render-parameter=inputMessage should be the same in the sender and receiver portlets’ container component property to achieve IPC via the Public Render Parameter.

Once you deploy both portlets to your Liferay server, you can add both portlets to your Liferay page, as shown in Figures 4-2 and 4-3. If you add two portlets side by side, it will be more helpful to see the output.

A screenshot of the sender portlet on the Liferay D X P webpage. It has an option to save and asks to pass a message as a parameter.

Figure 4-2

Output screen of the ApressIPCSender portlet for PRP

A screenshot of the Apress I P C receiver portlet reads that a message has been received from the public render parameter.

Figure 4-3

Output screen of the ApressIPCReceiver portlet for PRP

When you submit text from the apressIPCSender portlet, that text will be received by the apressIPCReceiver portlet.

IPC via Private Session Attributes

This approach to inter-portlet communication is very similar to the previous approach. The only difference is that instead of specifying a public parameter, the complete portlet session is accessible to other portlets. The producer and consumer portlets can be available on the same page or another page.

Let’s look at this with an example that uses the same apressIPCSender and apressIPCReceiver portlets to achieve a Private Session Attributes (PSA) IPC.

In the apressIPCSender portlet controller, use the code shown in Listing 4-5 to achieve IPC via Private Session Attributes. You can see the highlighted property, which is vital to performing Private Session Attributes IPC, and this property should be the same in the sender and receivers portlets.
package com.apress.handsonliferay.ipc.sender.portlet;
import com.apress.handsonliferay.ipc.sender.constants.ApressIPCSenderPortletKeys;
import com.liferay.portal.kernel.portlet.bridges.mvc.MVCPortlet;
import com.liferay.portal.kernel.util.ParamUtil;
import java.io.IOException;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.Portlet;
import javax.portlet.PortletException;
import javax.portlet.PortletSession;
import javax.portlet.ProcessAction;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.xml.namespace.QName;
import org.osgi.service.component.annotations.Component;
/**
 * @author Inthiyaz
 */
@Component(
        immediate = true,
        property = {
                "com.liferay.portlet.display-category=category.ipc",
                "com.liferay.portlet.header-portlet-css=/css/main.css",
                "com.liferay.portlet.instanceable=true",
                "com.liferay.portlet.private-session-attributes=false",
                "javax.portlet.display-name=ApressIPCSender",
                "javax.portlet.init-param.template-path=/",
                "javax.portlet.init-param.view-template=/view.jsp",
                "javax.portlet.name=" + ApressIPCSenderPortletKeys.APRESSIPCSENDER,
                "javax.portlet.resource-bundle=content.Language",
                "javax.portlet.security-role-ref=power-user,user",
                "javax.portlet.supported-public-render-parameter=inputMessage",
                "javax.portlet.supported-publishing-event=produceMessage;http://inthiyaz.com"
        },
        service = Portlet.class
)
public class ApressIPCSenderPortlet extends MVCPortlet {
        public void passMessage(ActionRequest actionRequest, ActionResponse actionResponse)
                        throws IOException, PortletException {
                // TODO Auto-generated method stub
                String passMessageVal = ParamUtil.getString(actionRequest, "inputMessage","");
                actionResponse.getRenderParameters().setValue("inputMessage", passMessageVal);
        }
        @Override
        public void doView(RenderRequest renderRequest, RenderResponse renderResponse)
                        throws IOException, PortletException {
                // TODO Auto-generated method stub
                PortletSession portletSession = renderRequest.getPortletSession();
                portletSession.setAttribute("Liferay_Shared_Session_Para", "Session Param", PortletSession.APPLICATION_SCOPE);
                super.doView(renderRequest, renderResponse);
        }
}
Listing 4-5

ApressIPCSenderPortlet Controller Class for PSA

In the apressIPCReceiver portlet, use the code shown in Listings 4-6 and 4-7 to achieve IPC via Private Session Attributes. Observe that com.liferay.portlet.private-session-attributes=false should be identical in the sender and receiver portlets container’s component property to achieve IPC via Private Session Attributes.
<%@ include file="/init.jsp" %>
        <h5><liferay-ui:message key="apressipcreceiver.caption"/></h5>
        <h2>Received Message from Public Render Parameter :<div style="color:blue">  ${recievedMessage}</div></h2>
        <h2>Received Message from Session Parameter :<div style="color:red">  ${recievedSessionMessage}</div></h2>
Listing 4-6

View.jsp of apressIPCReceiver Portlet for PSA

package com.apress.handsonliferay.ipc.receiver.portlet;
import com.apress.handsonliferay.ipc.receiver.constants.ApressIPCReceiverPortletKeys;
import com.liferay.portal.kernel.portlet.bridges.mvc.MVCPortlet;
import com.liferay.portal.kernel.util.ParamUtil;
import java.io.IOException;
import javax.portlet.Event;
import javax.portlet.EventRequest;
import javax.portlet.EventResponse;
import javax.portlet.Portlet;
import javax.portlet.PortletException;
import javax.portlet.PortletSession;
import javax.portlet.ProcessEvent;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.osgi.service.component.annotations.Component;
/**
 * @author Inthiyaz
 */
@Component(
        immediate = true,
        property = {
                "com.liferay.portlet.display-category=category.ipc",
                "com.liferay.portlet.header-portlet-css=/css/main.css",
                "com.liferay.portlet.instanceable=true",
                "com.liferay.portlet.private-session-attributes=false",
                "javax.portlet.display-name=ApressIPCReceiver",
                "javax.portlet.init-param.template-path=/",
                "javax.portlet.init-param.view-template=/view.jsp",
                "javax.portlet.name=" + ApressIPCReceiverPortletKeys.APRESSIPCRECEIVER,
                "javax.portlet.resource-bundle=content.Language",
                "javax.portlet.security-role-ref=power-user,user",
                "javax.portlet.supported-public-render-parameter=inputMessage",
                "javax.portlet.supported-processing-event=produceMessage;http://inthiyaz.com"
        },
        service = Portlet.class
)
public class ApressIPCReceiverPortlet extends MVCPortlet {
        @Override
        public void doView(RenderRequest renderRequest, RenderResponse renderResponse)
                        throws IOException, PortletException {
                // TODO Auto-generated method stub
                String passMessageVal =  ParamUtil.getString(renderRequest, "inputMessage","");
                System.out.println("Received Message "+passMessageVal);
                renderRequest.setAttribute("recievedMessage", passMessageVal);
                PortletSession portletSession = renderRequest.getPortletSession();
                renderRequest.setAttribute("recievedSessionMessage",
                                (String)portletSession.getAttribute("Liferay_Shared_Session_Para",PortletSession.APPLICATION_SCOPE));
                super.doView(renderRequest, renderResponse);
                }
}
Listing 4-7

ApressIPCReceiverPortlet Controller Class for PSA

After you deploy these portlets in your Liferay server, you will see the output shown in Figure 4-4.

A screenshot of the Apress I P C sender and Apress I P C receiver portlets on the Liferay D X P webpage displaying a message from the session parameter.

Figure 4-4

Output screens of ApressIPCSender and ApressIPCReceiver portlets for PSA

IPC via Server-Side Events

Events-based IPC was introduced in Portlet Specification 2.0 (JSR 286). This new approach was implemented using server-side events. According to this mechanism, the producer portlet produces a possibility that other consumer portlets can consume. Figure 4-5 shows the working flow of IPC via server-side events.

An illustration of data flow between 2 portlets. In portlet A, the producer, points to process the event. In portlet B, the consumer, with an event payload arrow to render the process.

Figure 4-5

Working flow of IPC via server-side events

By default, this mode also works on portlets added to the same page. But using the following property in portal-ext.properties, you can access the portal.
portlet.event.distribution=layout-set
Let’s illustrate this concept with an example. In the apressIPCSender portlet, use the code shown in Listings 4-8 and 4-9 to achieve IPC via server-side events.
<%@ include file="/init.jsp" %>
<div style="padding: 15px">
        <h3><liferay-ui:message key="apressipcsender.caption"/></h3>
        <portlet:actionURL name="passMessage" var="passMessageURL" />
        <aui:form name="ipcForm" action="${passMessageURL}" method="post">
                <aui:input name="inputMessage" type="text" label=" Pass Message as Parameter"></aui:input>
                <aui:button type="submit"></aui:button>
        </aui:form>
        <portlet:actionURL name="producerevent" var="producerURL" />
        <aui:form name="ipceventForm" action="${producerURL}" method="post">
                <aui:input name="eventmessage" type="text" label=" Pass Message for Event"></aui:input>
                <aui:button type="submit"></aui:button>
        </aui:form>
</div>
Listing 4-8

View.jsp of the apressIPCSender Portlet for an Event

package com.apress.handsonliferay.ipc.sender.portlet;
import com.apress.handsonliferay.ipc.sender.constants.ApressIPCSenderPortletKeys;
import com.liferay.portal.kernel.portlet.bridges.mvc.MVCPortlet;
import com.liferay.portal.kernel.util.ParamUtil;
import java.io.IOException;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.Portlet;
import javax.portlet.PortletException;
import javax.portlet.PortletSession;
import javax.portlet.ProcessAction;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.xml.namespace.QName;
import org.osgi.service.component.annotations.Component;
/**
 * @author Inthiyaz
 */
@Component(
        immediate = true,
        property = {
                "com.liferay.portlet.display-category=category.ipc",
                "com.liferay.portlet.header-portlet-css=/css/main.css",
                "com.liferay.portlet.instanceable=true",
                "com.liferay.portlet.private-session-attributes=false",
                "javax.portlet.display-name=ApressIPCSender",
                "javax.portlet.init-param.template-path=/",
                "javax.portlet.init-param.view-template=/view.jsp",
                "javax.portlet.name=" + ApressIPCSenderPortletKeys.APRESSIPCSENDER,
                "javax.portlet.resource-bundle=content.Language",
                "javax.portlet.security-role-ref=power-user,user",
                "javax.portlet.supported-public-render-parameter=inputMessage",
                "javax.portlet.supported-publishing-event=produceMessage;http://inthiyaz.com"
        },
        service = Portlet.class
)
public class ApressIPCSenderPortlet extends MVCPortlet {
        public void passMessage(ActionRequest actionRequest, ActionResponse actionResponse)
                        throws IOException, PortletException {
                // TODO Auto-generated method stub
                String passMessageVal = ParamUtil.getString(actionRequest, "inputMessage","");
                actionResponse.getRenderParameters().setValue("inputMessage", passMessageVal);
        }
        @Override
        public void doView(RenderRequest renderRequest, RenderResponse renderResponse)
                        throws IOException, PortletException {
                // TODO Auto-generated method stub
                PortletSession portletSession = renderRequest.getPortletSession();
                portletSession.setAttribute("Liferay_Shared_Session_Para", "Session Param", PortletSession.APPLICATION_SCOPE);
                super.doView(renderRequest, renderResponse);
        }
        @ProcessAction(name = "producerevent")
        public void producerEvent(ActionRequest actionRequest,ActionResponse actionResponse) {
                String message = ParamUtil.getString(actionRequest, "eventmessage","");
                QName qName = new QName("http://inthiyaz.com","produceMessage");
                System.out.println("----"+message);
                actionResponse.setEvent(qName, message);
        }
}
Listing 4-9

ApressIPCSenderPortlet Controller Class for an Event

In the apressIPCReceiver portlet, use the code shown in Listings 4-10 and 4-11 to achieve IPC via server-side events.
<%@ include file="/init.jsp" %>
        <h5><liferay-ui:message key="apressipcreceiver.caption"/></h5>
        <h2>Received Message from Public Render Parameter :<div style="color:blue">  ${recievedMessage}</div></h2>
        <h2>Received Message from Session Parameter :<div style="color:red">  ${recievedSessionMessage}</div></h2>
        <h2>Received Message from Event Parameter : <div style="color:green"> ${message} </div></h2>
Listing 4-10

View.jsp of the apressIPCReceiver Portlet for an Event

package com.apress.handsonliferay.ipc.receiver.portlet;
import com.apress.handsonliferay.ipc.receiver.constants.ApressIPCReceiverPortletKeys;
import com.liferay.portal.kernel.portlet.bridges.mvc.MVCPortlet;
import com.liferay.portal.kernel.util.ParamUtil;
import java.io.IOException;
import javax.portlet.Event;
import javax.portlet.EventRequest;
import javax.portlet.EventResponse;
import javax.portlet.Portlet;
import javax.portlet.PortletException;
import javax.portlet.PortletSession;
import javax.portlet.ProcessEvent;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.osgi.service.component.annotations.Component;
/**
 * @author Inthiyaz
 */
@Component(
        immediate = true,
        property = {
                "com.liferay.portlet.display-category=category.ipc",
                "com.liferay.portlet.header-portlet-css=/css/main.css",
                "com.liferay.portlet.instanceable=true",
                "com.liferay.portlet.private-session-attributes=false",
                "javax.portlet.display-name=ApressIPCReceiver",
                "javax.portlet.init-param.template-path=/",
                "javax.portlet.init-param.view-template=/view.jsp",
                "javax.portlet.name=" + ApressIPCReceiverPortletKeys.APRESSIPCRECEIVER,
                "javax.portlet.resource-bundle=content.Language",
                "javax.portlet.security-role-ref=power-user,user",
                "javax.portlet.supported-public-render-parameter=inputMessage",
                "javax.portlet.supported-processing-event=produceMessage;http://inthiyaz.com"
        },
        service = Portlet.class
)
public class ApressIPCReceiverPortlet extends MVCPortlet {
        @Override
        public void doView(RenderRequest renderRequest, RenderResponse renderResponse)
                        throws IOException, PortletException {
                // TODO Auto-generated method stub
                String passMessageVal =  ParamUtil.getString(renderRequest, "inputMessage","");
                System.out.println("Received Message "+passMessageVal);
                renderRequest.setAttribute("recievedMessage", passMessageVal);
                PortletSession portletSession = renderRequest.getPortletSession();
                renderRequest.setAttribute("recievedSessionMessage",
                                (String)portletSession.getAttribute("Liferay_Shared_Session_Para",PortletSession.APPLICATION_SCOPE));
                super.doView(renderRequest, renderResponse);
                }
        @ProcessEvent(qname = "{http://inthiyaz.com}produceMessage")
                public void consumeMessage(EventRequest eventRequest,EventResponse eventResponse) {
                Event event = eventRequest.getEvent();
                String receivedValue = (String)event.getValue();
                eventRequest.setAttribute("message", receivedValue);
                System.out.println("Event Message "+ receivedValue);
        }
}
Listing 4-11

ApressIPCReceiverPortlet Controller Class for an Event

You can observe that javax.portlet.supported-publishing-event=produceMessage;http://inthiyaz.com should be identical in the sender and receiver portlets container’s component property to achieve IPC via Private Session Attributes.

When you deploy both portlets in your Liferay server, you will see the output shown in Figure 4-6.

A screenshot of portlets on the Liferay D R X webpage. The sender sends messages as a parameter for an event and the receiver gets messages from session and event parameters.

Figure 4-6

Output screens of ApressIPCSender and ApressIPCReceiver Portlets for an event

Client-Side IPC via Ajax

Client-Side inter-portlet communication is one of the approaches wherein IPC is achieved from the client side instead of the server side. This is one of two approaches to achieve this. Client-side IPC can communicate with a portlet on the same page. Because client-side IPC uses JavaScript to communicate, all portlets that participate in IPC must be on the same page. Liferay provides a JavaScript library for its implementation. The client-side IPC using Ajax is very similar to event-based IPC; the difference is that this is achieved by Ajax instead of by a server-side event.

Let’s look at this with an example. In the apressIPCSender portlet, use the following code to achieve IPC via client-side Ajax.
Liferay.fire('eventName',{
        parameter1: value1,
        parameter2: value2
});
In the apressIPCReceiver portlet, use the following code to achieve IPC via client-side Ajax.
Liferay.on('eventName',function(event) {
   var firstValue = event.parameter1,
   var  secondValue = event.parameter2
});
Note

There may be one sender and many receivers, but the sender and receivers must be on the same page.

Client-Side IPC via Cookies

Like client-side IPC with Ajax, you can achieve IPC through browser cookies. The data is stored in cookies, and other portlets will access this information. However, this comes with limitations regarding data privacy, which is why this is rarely used. Another shortcoming is that if cookie access is disabled on the browser, this will fail. There are several ways to achieve this, as you can set and read cookies using plain JavaScript or jQuery. The following sample code in JavaScript illustrates this approach.

Setting the cookie:
function setIPCCookie() {
   document.cookie = "bookName=Hands On Liferay DXP";
}
Reading the cookie:
function getIPCCookie() {
  var cookies = "; " + document.cookie;
  var keyValues = cookies.split(";bookName=");
  if (keyValues.length == 2)
     return keyValues.pop().split(";").shift();
  else
     return '';
}

This explains how IPC can be achieved with different approaches; in the next section, you learn about the Liferay message bus.

Liferay Message Bus

Liferay’s message bus is a loosely coupled approach to exchanging messages. This loose coupling lets message producers continue to the next task/instruction while the consumer gets the message and starts processing it. Inherently, this is a service-level API that components can use to send and receive messages. This Message Bus API is part of Liferay’s global class loader, making it accessible across the Liferay server. This works well in a clustered environment.

The message bus is very helpful in scenarios where you must do parallel processing. For example, you need to update the search index once the data is updated, but user flow need not stop for it. You can pass messages to a message bus with essential data to update the search index, while the user flow can return with the data update process.

The message bus system contains the following components:

  • Message bus: The message bus ensures the transfer of messages between senders and listeners. The following code is used to create a message bus.

  • com.liferay.portal.kernel.messaging.MessageBus. .sendMessage(destinationName, messageobj)

  • Destination: A destination is an address to which listeners are registered to receive messages. Message bus destinations are based on the destination configurations and registered as OSGI services, and they detect the destination services and manage their associated destinations. There are three types of destinations:
    • Parallel destination

    • Serial destination

    • Synchronous destination

  • Destinations are based on the DestinationConfiguration (com.liferay.portal.kernel.messaging.DestinationConfiguration), which provides three static methods for creating various types of destinations:
    • createParallelDestinationConfiguration(String destinationName)

    • createSerialDestinationConfiguration(String destinationName)

    • createSynchronousDestinationConfiguration(String destinationName)

  • Listener: A listener is the receiver of the messages. It consumes messages received at destinations and processes them. In Liferay, you have three ways for message listeners to listen to messages:
    • Automatic Registration as a Component (see Listing 4-12)

package com.handsonliferay.messagebus.event.listener;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.messaging.Message;
import com.liferay.portal.kernel.messaging.MessageListener;
public class MessageBusRegisteredSynchronousMessageListener implements MessageListener {
        @Override
        public void receive(Message message) {
    //Do your job here
                try {
                        _log.info("Message::"+message);
                }
                catch (Exception e) {
                        e.printStackTrace();
                }
        }
        private static final Log _log = LogFactoryUtil.getLog
                (MessageBusRegisteredSynchronousMessageListener.class);
}
Listing 4-12

MessageBusRegisteredSynchronousMessageListener Class

  • Registering via a message bus reference (see Listing 4-13)

package com.handsonliferay.messagebus.register;
import com.handsonliferay.messagebus.event.listener.MessageBusRegisteredParallelMessageListener;
import com.handsonliferay.messagebus.event.listener.MessageBusRegisteredSerialMessageListener;
import com.handsonliferay.messagebus.event.listener.MessageBusRegisteredSynchronousMessageListener;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.messaging.MessageBus;
import com.liferay.portal.kernel.messaging.MessageListener;
import com.handsonliferay.messagebus.constants.LiferayMessageBusPortletKeys;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
@Component (
            immediate = true,
            service = MessageBusRegistrator.class
        )
public class MessageBusRegistrator{
    @Reference
    private MessageBus _messageBus;
    private MessageListener _messageListenerParallel;
    private MessageListener _messageListenerSerail;
    private MessageListener _messageListenerSynchronius;
        private static final Log _log = LogFactoryUtil.getLog(MessageBusRegistrator.class);
        @Activate
    protected void activate() {
                _messageListenerParallel = new MessageBusRegisteredParallelMessageListener();
        _messageBus.registerMessageListener(LiferayMessageBusPortletKeys.DESTINATION_PARALLEL, _messageListenerParallel);
        _log.info("Message Listener Registered.."+_messageListenerParallel);
        _messageListenerSerail = new MessageBusRegisteredSerialMessageListener();
        _messageBus.registerMessageListener(LiferayMessageBusPortletKeys.DESTINATION_SERIAL, _messageListenerSerail);
        _log.info("Message Listener Registered.."+_messageListenerSerail);
        _messageListenerSynchronius = new MessageBusRegisteredSynchronousMessageListener();
        _messageBus.registerMessageListener(LiferayMessageBusPortletKeys.DESTINATION_SYNCHRONOUS, _messageListenerSynchronius);
        _log.info("Message Listener Registered.."+_messageListenerSynchronius);
    }
    @Deactivate
    protected void deactivate() {
        _messageBus.unregisterMessageListener(LiferayMessageBusPortletKeys.DESTINATION_PARALLEL,  _messageListenerParallel);
        _log.info("Message Listener Unregistered.."+_messageListenerParallel);
        _messageBus.unregisterMessageListener(LiferayMessageBusPortletKeys.DESTINATION_SERIAL,  _messageListenerSerail);
        _log.info("Message Listener Unregistered.."+_messageListenerSerail);
        _messageBus.unregisterMessageListener(LiferayMessageBusPortletKeys.DESTINATION_SYNCHRONOUS,  _messageListenerSynchronius);
        _log.info("Message Listener Unregistered.."+_messageListenerSynchronius);
    }
}
Listing 4-13

MessageBusRegistrator Class

  • Registering directly to the destination (see Listing 4-14)

package com.handsonliferay.messagebus.register;
import com.handsonliferay.messagebus.event.listener.DestinationRegisteredParallelMessageListener;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.messaging.Destination;
import com.liferay.portal.kernel.messaging.MessageListener;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
@Component (
            immediate = true,
            service = DestinationListenerRegistrator.class
        )
public class DestinationListenerRegistrator{
    private MessageListener _messageListener;
        private static final Log _log = LogFactoryUtil.getLog(DestinationListenerRegistrator.class);
    @Reference(target = "(destination.name="+"Destinationmessage"+")")
    private Destination _destinationParellel;
        @Activate
    protected void activate() {
                _messageListener = new DestinationRegisteredParallelMessageListener();
                _destinationParellel.register(_messageListener);
        _log.info("Message Listener Registered.."+_messageListener);
    }
    @Deactivate
    protected void deactivate() {
        _destinationParellel.unregister(_messageListener);
        _log.info("Message Listener Unregistered.."+_messageListener);
    }}
Listing 4-14

DestinationListenerRegistrator Class

  • Sender: A sender is the one who sends messages to start the processing in the listener. This is the one who initiates the process.

You can see this whole process with the help of Figure 4-7. As the name suggests, you can send messages directly using the message bus. Use the same MessageSenderServiceImpl class for the two types of message busses as well (see Listings 4-15 and 4-16).

An illustration of the data flow between 2 modules of sender and receiver each through a message bus, which has destinations A, B, C. In listener module 1 and 2 is connected.

Figure 4-7

Message bus

Synchronous Message Bus

As the name suggests, the sender will sync with the listener until the message processing is complete. Here, the sender waits until it gets a response from the listener. The example code in Listing 4-16 helps illustrate this concept.
package com.handsonliferay.messagebus.service;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.messaging.Destination;
import com.liferay.portal.kernel.messaging.DestinationStatistics;
import com.liferay.portal.kernel.messaging.Message;
import com.liferay.portal.kernel.messaging.MessageBus;
import com.liferay.portal.kernel.messaging.MessageBusException;
import com.liferay.portal.kernel.messaging.MessageBusUtil;
import com.liferay.portal.kernel.messaging.MessageListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(
            immediate = true,
            service = MessageSenderServiceImpl.class
        )
public class MessageSenderServiceImpl{
        @Reference
    private MessageBus _messageBus;
        private static final Log _log = LogFactoryUtil.getLog(MessageSenderServiceImpl.class);
        public void sendMessageToDestination(String message,String destinationName) {
        Message messageobj = new Message();
        messageobj.put("message", message);
        _messageBus.sendMessage(destinationName, messageobj);
    }
                public void sendSynchronousMessageToDestination(String message,String destinationName) {
        Message messageobj = new Message();
        messageobj.put("message", message);
        try {
                        MessageBusUtil.sendSynchronousMessage(destinationName, messageobj);
                        //MessageBusUtil.sendSynchronousMessage(destinationName, message, timeout)
                } catch (MessageBusException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
    }
}
Listing 4-15

MessageSenderServiceImpl Class for a Synchronous Message

Asynchronous Message Bus

Here, the sender and listener are not in sync. The sender can continue processing without waiting for a response from the listener, referred to as send and forget asynchronous message bus. It is possible to receive a notification in the form of a callback, also referred to as a callback asynchronous message bus. The sender can also be configured to receive a callback. Listing 4-16 shows an example of this.
package com.handsonliferay.messagebus.service;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.messaging.Destination;
import com.liferay.portal.kernel.messaging.DestinationStatistics;
import com.liferay.portal.kernel.messaging.Message;
import com.liferay.portal.kernel.messaging.MessageBus;
import com.liferay.portal.kernel.messaging.MessageBusException;
import com.liferay.portal.kernel.messaging.MessageBusUtil;
import com.liferay.portal.kernel.messaging.MessageListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(
            immediate = true,
            service = MessageSenderServiceImpl.class
        )
public class MessageSenderServiceImpl{
        @Reference
    private MessageBus _messageBus;
        private static final Log _log = LogFactoryUtil.getLog(MessageSenderServiceImpl.class);
        public void sendMessageToDestination(String message,String destinationName) {
        Message messageobj = new Message();
        messageobj.put("message", message);
        _messageBus.sendMessage(destinationName, messageobj);
    }
        public DestinationStatistics getDestinationStatistics(String destinationName) {
                Destination destination = _messageBus.getDestination(destinationName);
                Set<MessageListener> listeners = destination.getMessageListeners();
                for (MessageListener curListener : listeners) {
             }
                DestinationStatistics dstatiStrics = destination.getDestinationStatistics();
                return dstatiStrics;
    }
        public List<String> getListeners(String destinationName) {
                Destination destination = _messageBus.getDestination(destinationName);
                Set<MessageListener> listeners = destination.getMessageListeners();
                List<String> listenersList = new ArrayList<String>();
                for (MessageListener curListener : listeners) {
                        listenersList.add(curListener.toString());
             }
                return listenersList;
    }
        public void sendSynchronousMessageToDestination(String message,String destinationName) {
        Message messageobj = new Message();
        messageobj.put("message", message);
        try {
                        MessageBusUtil.sendSynchronousMessage(destinationName, messageobj);
                        //MessageBusUtil.sendSynchronousMessage(destinationName, message, timeout)
                } catch (MessageBusException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
    }
}
Listing 4-16

MessageSenderServiceImpl Class for Synchronous and Asynchronous Messages

This explains how the Liferay message bus can be implemented; in the next section, you learn about the Liferay scheduler.

Liferay Scheduler

Schedulers do a simple job—they run a piece of code at a certain point in time, in a time-set repetition. Confused?? Let’s look at this more closely. Schedules are the core of many applications worldwide. They can be used for publishing reports, sending subscription emails, running batch jobs, and similar tasks. These tasks need to run monthly, weekly, and so on. A scheduler automates these tasks. A scheduler is nothing but a function invoked at a certain time instead of by any action, and that’s why it is called a scheduler. Liferay DXP provides excellent support for developing schedulers. Figure 4-8 shows the working flow of the Liferay scheduler.

A diagram explains the flow of codes from the database to the Liferay scheduler, the Liferay bus, and finally to its destination, the message listener.

Figure 4-8

Working flow of the Liferay scheduler

Liferay’s scheduler mechanism was developed on top of the Quartz scheduler engine, which is an open-source Java implementation for scheduling jobs. Liferay’s implementation of Quartz makes it simple to use and easy to implement, which you’ll see soon.

Before starting the implementation, you should know about the storage types. Liferay DXP provides three storage types:
  1. 1.

    Memory (StorageType.MEMORY): This storage type does not persist job information anywhere (only in memory) that is not cluster-aware. So, if there is a server outage at the scheduled time, the scheduler will not run, and in case it is used in a clustered environment, it will run n number of times, causing duplicate execution of code, where n is the number of nodes in the cluster.

     
  2. 2.

    Memory Clustered (StorageType.MEMORY_CLUSTERED): This storage type is similar to the previous MEMORY storage type, where the job information is kept in memory. Still, the only difference is that this type is cluster-aware. This means it will be affected during outages but will not rerun in a clustered environment. This is the default storage type.

     
  3. 3.

    StorageType.PERSISTED: In contrast to the MEMORY storage type, job details are persisted in the database. This takes care of the missed job in the case of outages and it is cluster-aware. This information is stored in the database tables.

     

In the case of the clustered environment, using either MEMORY_CLUSTERED or PERSISTED is recommended to ensure your job doesn’t run on every node. The MEMORY type is recommended if the job needs to run on all the nodes of the cluster (such as taking periodic thread dumps, and so on).

Let’s implement some code to get this running (see Listing 4-17).
package com.handsonliferay.scheduler.service;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.messaging.Message;
import com.liferay.portal.kernel.scheduler.SchedulerEngineHelper;
import com.liferay.portal.kernel.scheduler.SchedulerException;
import com.liferay.portal.kernel.scheduler.StorageType;
import com.liferay.portal.kernel.scheduler.Trigger;
import com.liferay.portal.kernel.scheduler.TriggerFactory;
import java.util.TimeZone;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(
            immediate = true,
            service = SchedulerServiceImpl.class
        )
public class SchedulerServiceImpl{
        public void createSchedule(String jobName, String groupName, String cron, String destinationName, String description)
                        throws SchedulerException {
                Trigger trigger = _triggerFactory.createTrigger(jobName,groupName,null,null,cron,TimeZone.getDefault());
                Message message = new Message();
                message.put("data",jobName+":"+groupName);
                _schedulerEngineHelper.schedule(trigger, StorageType.PERSISTED, description, destinationName, message, exceptionsMaxSize);
        }
        public static int exceptionsMaxSize = 10;
        @Reference
        private SchedulerEngineHelper _schedulerEngineHelper;
        @Reference
        private TriggerFactory _triggerFactory;
        private static final Log _log = LogFactoryUtil.getLog(SchedulerServiceImpl.class);
}
Listing 4-17

SchedulerServiceImpl Class for Scheduler

Summary

This chapter covered a few advanced concepts of Liferay DXP. Inter-portlet communication is practically vital for real-world applications, and in most applications, IPC via events is used because of its versatility. Message busses also play a vital role, and the most commonly implemented form is the asynchronous message bus. It’s primarily used for sending bulk mail or batch-processing tasks. The scheduler implementation manages the execution of scheduled tasks, such as periodic reports.

In the next chapter, you will learn about the Liferay Service Builder.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset