Monday 17 June 2013

How to write Simple/Cron Scheduler in Liferay ?

Many time, it requires to execute some code on regular interval or at some specific time.

For example : mail for reminders, weekly subscriptions, deleting unused data, generating reports.

Liferay comes with very easy implementation strategy for such requirements with scheduler.

In Liferay portlet, one can write scheduler easily.


1.  Create one java class in your portlet which implements    com.liferay.portal.kernel.messaging.MessageListener interface.

 public class Scheduler implements MessageListener {  
      public void receive(Message arg0) throws MessageListenerException {  
       // TODO Auto-generated method stub : your job code  
       //Write code : what you want to execute on timely basis.  
   }  
   }  


The code written in receive method would be executes on time basis.This class will receive
a message at a regular interval specified by the trigger element[liferay-portlet.xml].

In liferay-portlet.xml provide this class entry as shown below.


2.  Job Schedule timing can be configured in liferay-portlet.xml by trigger element as below.
  <scheduler-entry>  
     <scheduler-event-listener-class>com.example.schedule.Scheduler</scheduler-event-listener- class>  
        <trigger>  
         <simple>  
         <simple-trigger-value>1</simple-trigger-value>  
         <!-- this would be some number,-->  
         <!--Based in above configurations scheduler will run in every one minute.-->  
         <time-unit>minute</time-unit>  
          <!-- time unit can be day","hour", "minute", "second", or "week"-->  
         </simple>  
       </trigger>  
   </scheduler-entry>  


You can either provide <simple-trigger-value> or <property-key>.

If you provided property-key like <property-key>interval</property-key>, the property-key value specifies a property key that will be queried from portal.properties or portlet.properties to create a trigger. For Example: if you provide <property-key>interval</property-key> and provide interval=3 in portlet.properties then job will run after each 3 minutes.


But sometime, you may need that scheduler job should not just run after particular interval, but you need that at specific time [say 11 :20PM, each tuesday ] job should run. Trigger specified in above code will not work in that case. You can specify cron trigger for such scenario.

 <trigger>  
         <cron>  
           <cron-trigger-value>50 * * ? * * *</cron-trigger-value>  
         </cron>  
 </trigger>   


[above cron text specify : every year,every month,every day,every hour, every minute on 50's second]

You can also use property-key instead cron-trigger-value.

cron-trigger-value /property value should be in cron-text format :

 MIN HOUR DAYOFMONTH MONTH DAYOFWEEK YEAR
Some Example Cron-text:
"0 0 12 * * ? *"                    Fire at 12pm (noon) every day
"0 15 10 ? * * *"                  Fire at 10:15am every day
"0 10,44 14 ? 3 WED *"     Fire at 2:10pm and at 2:44pm every Wednesday in the month of March.
"0 15 10 ? * MON-FRI *"  Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday

NOTE: Support for specifying both a day-of-week and a day-of-month value is not complete (you'll need to use the '?' character in on one of these fields).

Thus, you can easily write your custom scheduler in Liferay.
Example code available at exampleScheduler-portlet

Enjoy Learning!









Tuesday 4 June 2013

DynamicQuery in Liferay


Liferay provides very powerful tool service-builder in-order to deal with custom database entity.
It generates many methods to retrieve results from database just like you can have your own finder methods on entity.

Intro and Why?
But, this not suffice all we may need. Many times we would need complex result set from database table by performing aggregate SQL operations such as max,avg etc, and also in clause, and/or operations or we would need to fetch some of fields rather the whole entity object[database entity mapped object].

Here, DynamicQuery would be useful.

How?

Dealing with Liferay's built in entities like Users, Organizations, Assets, AssetCategories
As DynamicQuery API looks for current classloader, for access, but as you are dealing with Portal level class [User.class], you have to specify portalClassLoader as below.

 ClassLoader portalClassLoader = PortalClassLoaderUtil.getClassLoader();  
 DynamicQuery query = DynamicQueryFactoryUtil.forClass(User.class, portalClassLoader);  


Dealing with custom entity within portlet.

DynamicQuery dynamicQuery=DynamicQueryFactoryUtil.forClass(Application.class);

Here we have just got dynamicQuery object and having applied any query criteria.
Lets do that.

Following util classes would be useful for it.

PropertyFactoryUtil.java, RestrictionsFactoryUtil.java, ProjectionFactoryUtil.java, OrderFactoryUtil.java

Lets understand by example.

 DynamicQuery dynamicQuery=DynamicQueryFactoryUtil.forClass(CustomEntity.class);  
 dynamicQuery.add(RestrictionsFactoryUtil.eq("status", "active"));  
 dynamicQuery.addOrder(OrderFactoryUtil.asc("name"));  
 dynamicQuery.setLimit(0, 5) ;  
 List<CustomEntity> list=ApplicationLocalServiceUtil.dynamicQuery(dynamicQuery);  

Though above example is self-explanatory, let me tell that it will return list of 5 objects[of CustomEntity] who has status field as active and results will be ordered[ascending] by name field.

Above things can be easily achieved by finder on status field and having order on name in service.xml

Lets add some more complexity in our query.
Say, You need list of 5 CustomEntity having name starts with"ABC" and status as active and order by name.

Here you go.

 DynamicQuery dynamicQuery=DynamicQueryFactoryUtil.forClass(CustomEntity.class);  
 dynamicQuery.add(RestrictionsFactoryUtil.and(RestrictionsFactoryUtil.eq("status", "active"),RestrictionsFactoryUtil.ilike("name", "ABC")));  
 dynamicQuery.addOrder(OrderFactoryUtil.asc("name"));  
 dynamicQuery.setLimit(0, 5) ;  
 List<CustomEntity> list=ApplicationLocalServiceUtil.dynamicQuery(dynamicQuery);  


You can also add criteria like greater than, less than, in clause etc using RestrictionsFactoryUtil.

Let say now you need just some of tuples rather whole customEntity object.

dynamicQuery.setProjection(ProjectionFactoryUtil.projectionlist().add(ProjectionFactoryUtil.property("name")).add(ProjectionFactoryUtil.property("age")));

By setting projection as above, output result would be list of array of Objects.[with name,age values].


ProjectionFactoryUtil can be utilized to have aggregate functions like min,max,avg,distinct,count etc.

Hope this Helps .
Keep Learning.
Kindly let me know if any questions.