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.
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.