Sunday, August 1, 2021

Liferay Dynamic Schedule Jobs Implementation

Liferay is providing scheduler API to create schedule job in Liferay Portal Applications. Liferay internally uses the Quartz scheduler engine. Liferay also uses the Message Bus implementation with scheduler API.

 





 Software Stack


 

Liferay-ce-portal-7.4.1-ga2

 

 

 


Below Article will provide more details on Liferay Scheduler

 


http://www.liferaysavvy.com/2021/07/working-with-liferay-scheduler.html

 



Liferay API is using Message Bus for every scheduler job and following article will provide more detail about Liferay Message Bus.

 


http://www.liferaysavvy.com/2021/07/working-with-liferay-message-bus.html

 


http://www.liferaysavvy.com/2021/07/liferay-message-bus-implementation.html

 



Following are the steps to implement Dynamic schedule jobs in Liferay

 

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

 



Create Message Bus Destination

 

Create destination have following steps in OSGi module

 

  • Create Destination Configuration
  • Create Destination
  • Manage the Destination Object

 


Create Destination Configuration


Message Bus API is providing DestinationConfiguration class to create destination configuration. We can create different types of destination as it specified as below.

 


Parallel Destination Configuration


 

DestinationConfiguration destinationConfiguration =

              new DestinationConfiguration(

                   DestinationConfiguration.DESTINATION_TYPE_PARALLEL,LiferayMessageBusPortletKeys.DESTINATION_PARALLEL);

 

 

 

 


Serial Destination Configuration


 

DestinationConfiguration destinationConfiguration =

              new DestinationConfiguration(

                   DestinationConfiguration.DESTINATION_TYPE_SERIAL,LiferayMessageBusPortletKeys.DESTINATION_SERIAL);

 

 


Synchronous Destination Configuration


 

DestinationConfiguration destinationConfiguration =

              new DestinationConfiguration(

                   DestinationConfiguration.DESTINATION_TYPE_SYNCHRONOUS,LiferayMessageBusPortletKeys.DESTINATION_SYNCHRONOUS);

 

 

 

 



Create Destination


DestinationFactory will create destination based on configuration

 


 

Destination destination = _destinationFactory.createDestination(

              destinationConfiguration);

 

 



 

Register Destination as OSGi Service



ServiceRegistration<Destination> is used to register destination as OSGi service.


 

_destinationServiceRegistration = _bundleContext.registerService(

              Destination.class, destination, destinationProperties);

         _log.info("Destination is registred with Service Regisration ..");

 

 

 

 


Manage the Destination Object



We have to manage destination object so that it can deregister when bundle is deactivated.

 


 

Dictionary<String, Object> destinationProperties =

              HashMapDictionaryBuilder.<String, Object>put(

                  "destination.name", destination.getName()).build();

 

 

 


Destroy Destination


 

@Deactivate

     protected void deactivate() {

         if (_destinationServiceRegistration != null) {

              Destination destination = _bundleContext.getService(

                   _destinationServiceRegistration.getReference());

 

              _destinationServiceRegistration.unregister();

 

              destination.destroy();

         }

 

 

 


Setting Thread Pool for destination



 

destinationConfiguration.setMaximumQueueSize(_MAXIMUM_QUEUE_SIZE);

destinationConfiguration.setWorkersCoreSize(_CORE_SIZE);

destinationConfiguration.setWorkersMaxSize(_MAX_SIZE);

 

 

 


Rejection Handler to Handle Failed Messages

 


 

RejectedExecutionHandler rejectedExecutionHandler =

              new ThreadPoolExecutor.CallerRunsPolicy() {

 

                  @Override

                  public void rejectedExecution(

                       Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {

 

     if (_log.isWarnEnabled()) {

         _log.warn("The current thread will handle the request " + "because the rules engine's task queue is at " +"its maximum capacity");

       }

 

super.rejectedExecution(runnable, threadPoolExecutor);

 }

 

};

 

destinationConfiguration.setRejectedExecutionHandler(rejectedExecutionHandler);

 

 

 

 


Register Listener with Destination


Listener should implement MessageListener interface. We have to implement receive(..) method and there we implement our business logic. Listeners are registered with destination to received messages from senders.

 

 


There are different ways to register listener below is the one of the ways.

 


Automatic Registration


 

Create MessageListener component and pass destination name as property so that it will be register with destination automatically when component is create.



 

@Component (

         immediate = true,

         property = {"destination.name=liferaysavvy/synchronous-destination"},

         service = MessageListener.class

     )

public class AutomaticRegisteredSynchronousMessageListener implements MessageListener {

     @Override

     public void receive(Message message) {

 

         try {

              _log.info("Message::"+message);

             

         }

         catch (Exception e) {

              e.printStackTrace();

         }

        

     }

 

    

 

     private static final Log _log = LogFactoryUtil.getLog(

          AutomaticRegisteredSynchronousMessageListener.class);

 

}

 

 



Create Scheduler job and tag to Destination

 


SchedulerEngineHelperUtil is class will provide methods to create schedule Jobs. We can also use “SchedulerEngineHelper” OSGi reference to create same schedule jobs.

 


Following are API method to schedule Job in Liferay

 


public static void schedule(
      Trigger trigger
, StorageType storageType, String description,
     
String destinationName, Message message, int exceptionsMaxSize)
  
throws SchedulerException

 


public static void schedule(
      Trigger trigger
, StorageType storageType, String description,
      
String destinationName, Object payload, int exceptionsMaxSize)
  
throws SchedulerException

 


Following are the important Parameters

 


Trigger


Its cron trigger Object. Trigger factory will be used to create trigger object. It is required CRON expression and it should be valid quartz cron expression.

 


http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html

 

Storage Type


Its scheduler storage type like Memory or Persisted.

 


Destination Name


Its message bus destination where scheduler engine will send messages.

 


Message


It's JSON object to carry required data from Message Bus to Listener.

 


Liferay 7.x onwards Liferay scheduler API is providing the Timezone while creating Trigger and it will resolve the Daylight time zone issue for scheduled jobs.

 


 

public Trigger createTrigger(

                    String jobName, String groupName, Date startDate, Date endDate,

                    String cronExpression, TimeZone timeZone);

 

 


Example



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

         Trigger trigger = _triggerFactory.createTrigger(jobName,groupName,null,null,cron,TimeZone.getDefault());

 

 

@Reference

private TriggerFactory _triggerFactory;

      

 

 


Creating Schedule



 

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

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

 

 

@Reference

private SchedulerEngineHelper _schedulerEngineHelper;

 

 



Deploy and Run Example OSGi Module

 


Find OSGi module from the below Git repository and it will demonstrate, create dynamic schedule jobs.

 


https://github.com/LiferaySavvy/liferay-scheduler-example

 

 


Clone the project and import into your Liferay workspace.

 


Build and Deploy OSGi module with Gradle tasks.

 


Add Widget to Liferay Page

 




 

Access “Create Dynamic Schedule Jobs” UI  screen and create job

 




Once job is created, Job details will be stored in Quartz Database tables.

 




 


 

In the console logs, every 2 min Scheduler engine will trigger the job and send message to Message Bus. Other end listener will be receiving the message and print in the console logs.

 


 

 


2021-08-01 15:02:00.026 INFO  [liferaysavvy/parallel-destination-10][AutomaticRegisteredParellelMessageListener:34] Message::{destinationName=liferaysavvy/parallel-destination, response=null, responseDestinationName=null, responseId=null, payload=null, values={GROUP_NAME=Dynamic, companyId=20100, data=TwoMinJob:Dynamic, groupId=0, DESTINATION_NAME=liferaysavvy/parallel-destination, EXCEPTIONS_MAX_SIZE=10, JOB_STATE=com.liferay.portal.kernel.scheduler.JobState@31b87667, STORAGE_TYPE=PERSISTED, JOB_NAME=TwoMinJob}}

 

2021-08-01 15:04:00.025 INFO  [liferaysavvy/parallel-destination-11][AutomaticRegisteredParellelMessageListener:34] Message::{destinationName=liferaysavvy/parallel-destination, response=null, responseDestinationName=null, responseId=null, payload=null, values={GROUP_NAME=Dynamic, companyId=20100, data=TwoMinJob:Dynamic, groupId=0, DESTINATION_NAME=liferaysavvy/parallel-destination, EXCEPTIONS_MAX_SIZE=10, JOB_STATE=com.liferay.portal.kernel.scheduler.JobState@2dd02e8e, STORAGE_TYPE=PERSISTED, JOB_NAME=TwoMinJob}}

 


 

 

This is how we can create dynamic schedule job and every schedule job must be tagged with  destination.


 

Destinations are not persisted so it must be created when server startup or bundle is  activated. That’s reason we implemented destination creation as part of bundle activation. Schedule jobs are persisted as we are selected Persisted storage type.




Author

Recent Posts

Recent Posts Widget

Popular Posts