Thursday, July 29, 2021

Working with Liferay Scheduler

Scheduler is one of the important components in any application. To perform jobs periodically then application is required scheduler. Liferay portal is providing scheduler in portal and we can schedule jobs in portal applications.


Liferay implemented scheduler on top of the quartz scheduler engine. Quartz is popular open-source java implementation for scheduling jobs.


Liferay build another API on top native quartz so that we can easily configure or created schedule jobs in Liferay Portal Applications.



Software Stack


 

Liferay-ce-portal-7.4.1-ga2

 

 

 






Liferay Scheduler API is using Message Bus to delegate job to backend process.

 


High-level scheduler implementation in Liferay

 


  • Liferay provides scheduler API to create schedule job which is tagged to specific Message Bus destination.
  • Each destination has one or more message listeners register with destination. Destination may be Parallel or Serial.
  • Scheduler Engine will trigger Job at defined time frame based CRON expression.
  • Job is responsible to send message (Delegate job) to Message Bus on specific destination and that was already configured as part of scheduling.
  • Listener will receive the message which posted in Message Bus destination.
  • Once Listener is received the message then process the message and execute the further steps.

 


All business logic will be implemented in the listener so that it will be invoked when the message posted on the Message Bus. Sending message to Message Bus is configured by schedule job and scheduler engine will trigger job based on the cron expression.  Message Bus is really useful concept for scheduler to assign job in the blackened process.

 


Example


Sending email to group of users periodically like daily/weekly/month.



Liferay is providing different job storage types while creating schedule job.



Memory


When we use single liferay instance environment then memory storage type can be useful and all scheduled jobs information is stored in portal memory. If server restarted all scheduled jobs information will be lost.



Memory Cluster


Memory Cluster is similar to memory storage type but this is being used in Clustered environment. If job created in one instance memory that will be replicated in other instance memory. There are chances of loss of schedule job details if the both instances are crash or down.



Persisted


Persisted storage is another available type storage and job data will be stored in the database quartz tables. So, when the scheduler engine stared then all jobs will be scheduled based on the information available in the database. There is no loss of scheduled job data.



The following are the QUARTZ tabled created by Quartz scheduler during liferay startup.

 







 

 

When we create schedule job, we must select the storage type so that job details will be stored in either memory or database tables.

 


Clustered environment, jobs may fire from multiple instances so we have to be very careful. Quartz internally uses the locking mechanism so that only one scheduler instance fires the job and other instances never fire same job at same time.


 

The following is steps to create dynamic schedule jobs

 


  • Create Destination
  • Register Listener with Destination
  • Add destination to Message Bus
  • Create Scheduler job and tag to destination

 


Following is groovy code snipper to create schedule job and tag to Message Bus destination. Here is using the existing Dummy Message Listener to demonstrate the example. Usually, we will create our own message listener and implement business login as part of message listener.

 


Below schedule job is for every minute and for every minute scheduler engine post a message on Message Bus for specific destination and dummy message listener receive the message and execute the received method which prints the message object in logs.

 


Find script from below GitHub repository.

 


https://github.com/LiferaySavvy/liferay-admin-groovy-examples/blob/master/create-dynamic-quartz-schedule-job.groovy

 


OR

 


 

 


import com.liferay.portal.kernel.messaging.Destination;

import com.liferay.portal.kernel.messaging.DestinationConfiguration;

import com.liferay.portal.kernel.messaging.DestinationFactoryUtil;

import com.liferay.portal.kernel.messaging.DummyMessageListener;

import com.liferay.portal.kernel.messaging.Message;

import com.liferay.portal.kernel.messaging.MessageBusUtil;

import com.liferay.portal.kernel.messaging.MessageListener;

import com.liferay.portal.kernel.portlet.PortletClassLoaderUtil;

import com.liferay.portal.kernel.scheduler.SchedulerEngineHelperUtil;

import com.liferay.portal.kernel.scheduler.StorageType;

import com.liferay.portal.kernel.scheduler.Trigger;

import com.liferay.portal.kernel.scheduler.TriggerFactoryUtil;

import com.liferay.portal.kernel.util.PortalClassLoaderUtil;

 

String destinationName = "liferaysavvy/parallel-destination";

String className = DummyMessageListener.class.getName();

String jobName = "com.liferay.portal.kernel.messaging.DummyMessageListener";

String groupName = "com.liferay.portal.kernel.messaging.DummyMessageListener";

//Every minute

String cronExpression = "0 0/1 * * * ?";

String description = "Every one minute job using Parallel Destination..";

int exceptionsMaxSize = 10;

 

//Create destination and register listener

try {

          DestinationConfiguration destinationConfig =  DestinationConfiguration.createParallelDestinationConfiguration(destinationName);

          Destination parallelDestination = DestinationFactoryUtil.createDestination(destinationConfig);

         

          String portletId = null;

          ClassLoader classLoader = null;

          if(portletId != null){

                    //PortletClassLoaderUtil.setServletContextName(portletId);

                    classLoader = PortletClassLoaderUtil.getClassLoader(portletId);

          } else {

                    classLoader = PortalClassLoaderUtil.getClassLoader();

          }

          MessageListener messageListener = (MessageListener)classLoader.loadClass(className).newInstance();

          out.println("messageListener :: ${messageListener}");

          parallelDestination.register(messageListener);

          MessageBusUtil.addDestination(parallelDestination);

          //Create schedule job with cron expression.

 

          Trigger trigger = TriggerFactoryUtil.createTrigger(jobName,groupName,cronExpression);

          Message message = new Message();

          message.put("data","My Data required for job..");

          SchedulerEngineHelperUtil.schedule(trigger, StorageType.PERSISTED, description,destinationName, message, exceptionsMaxSize)

} catch (Exception e) {

          e.printStackTrace();

}

 


 

 

Global Menu --> Control Panel -->System -->Server Administration

 




 



 

Click on script, copy paste the above script in the editor and execute it.


Schedule job will be created and job details is stored in database.

 




 

 

 

We can see scheduled job details in the database table.

 




 

Cron trigger table have details of Trigger

 




 

 

Verify Dummy Listener logs



Created schedule job for every minute so scheduler engine will send message for every minute on given destination and Dummy Listener will receive the message object.


Enable info logs for “com.liferay.portal.kernel.messaging.DummyMessageListener

 


Global Menu --> Control Panel -->System -->Server Administration

 




 

 

 



 

 

Click on log Levels and Click on + Add Category

 




 

 

 

 

Provide logger name and Log levels as follows and save details. Now dummy listener will show all logs in the console.

 




 

 

 

Open server logs console and we can observe that every minute Dummy Message Listener is receiving message from Message Bus and print the message object in the logs.

 




 

This is how Liferay scheduler works and it used Native Quartz and Message Bus.

 


Following is sample groovy script to know the statistics of destination



 Execute from control panel


Global Menu --> Control Panel -->System -->Server Administration --> Script



Find script from below GitHub repository.

 


https://github.com/LiferaySavvy/liferay-admin-groovy-examples/blob/master/message-bus-destination-statistics.groovy

 

 


OR

 


 

 


import com.liferay.portal.kernel.messaging.Destination;

import com.liferay.portal.kernel.messaging.DestinationConfiguration;

import com.liferay.portal.kernel.messaging.DestinationFactoryUtil;

import com.liferay.portal.kernel.messaging.DummyMessageListener;

import com.liferay.portal.kernel.messaging.Message;

import com.liferay.portal.kernel.messaging.MessageBusUtil;

import com.liferay.portal.kernel.messaging.MessageListener;

import com.liferay.portal.kernel.portlet.PortletClassLoaderUtil;

import com.liferay.portal.kernel.scheduler.SchedulerEngineHelperUtil;

import com.liferay.portal.kernel.scheduler.StorageType;

import com.liferay.portal.kernel.scheduler.Trigger;

import com.liferay.portal.kernel.scheduler.TriggerFactoryUtil;

import com.liferay.portal.kernel.util.PortalClassLoaderUtil;

import com.liferay.portal.kernel.messaging.DestinationStatistics;

 

//Create destination and register listener

try {

                    String destinationName = "liferaysavvy/parallel-destination";

                    String className = DummyMessageListener.class.getName();

                    String jobName = "com.liferay.portal.kernel.messaging.DummyMessageListener";

                    String groupName = "com.liferay.portal.kernel.messaging.DummyMessageListener";

                    //Every minute

                    Destination destiNation = MessageBusUtil.getDestination(destinationName);

                    if(){

                              out.println(destiNation.getMessageListeners());

                              DestinationStatistics destinationStatistics = destiNation.getDestinationStatistics();

                             

                              out.println("Destination is registered with Messagebus::${destiNation.isRegistered()}");

                             

                              out.println("Sent Message Count    :: ${destinationStatistics.getSentMessageCount()}");

                              out.println("Pending Message Count :: ${destinationStatistics.getPendingMessageCount()}");

                              out.println("Active Thread Count   :: ${destinationStatistics.getActiveThreadCount()}");

                              out.println("Current Thread Count  :: ${destinationStatistics.getCurrentThreadCount()}");

                              out.println("Largest Thread Count  :: ${destinationStatistics.getLargestThreadCount()}");

                              out.println("Max Thread Pool Size  :: ${destinationStatistics.getMaxThreadPoolSize()}");

                              out.println("Min Thread Pool Size  :: ${destinationStatistics.getMinThreadPoolSize()}");

                    } else {

                              out.println("************Destination not in the Message Bus************"));

                    }

                   

                   

                   

                   

} catch (Exception e) {

          e.printStackTrace();

}

 


 



 

Important Points

 


Demonstration purpose, used Dummy Listener and groovy script to create schedule job. These messages bus destinations will be lost once restarted the server.

 


Realtime scenarios we have to create Destinations for every restart. We can use bundle activate method to create destinations and register listeners with listener component class.

 



Reference Code Points from Liferay

 


https://github.com/liferay/liferay-portal/blob/7.4.1-ga2/modules/apps/portal-scheduler/portal-scheduler-quartz/src/main/java/com/liferay/portal/scheduler/quartz/internal/QuartzSchedulerEngine.java

 


https://github.com/liferay/liferay-portal/blob/7.4.1-ga2/portal-kernel/src/com/liferay/portal/kernel/scheduler/StorageType.java

 


https://github.com/liferay/liferay-portal/blob/7.4.1-ga2/portal-kernel/src/com/liferay/portal/kernel/scheduler/SchedulerEngineHelperUtil.java

 


https://github.com/liferay/liferay-portal/blob/7.4.1-ga2/portal-kernel/src/com/liferay/portal/kernel/scheduler/TriggerFactoryUtil.java

 


https://github.com/liferay/liferay-portal/blob/7.4.1-ga2/modules/apps/portal-scheduler/portal-scheduler-quartz/src/main/java/com/liferay/portal/scheduler/quartz/internal/job/MessageSenderJob.java





Author

1 comment :

  1. Do not get me incorrect, there are millions of individuals with criminal orange county mugshots records that are relied on with crucial tasks daily. Heck, everybody makes errors or makes bad choices in life.

    ReplyDelete

Recent Posts

Recent Posts Widget

Popular Posts