Background Task - a.k.a the secret weapon behind the new asynchronous staging

Company Blogs May 8, 2014 By Dániel Kocsis Staff

The more refined staging along with Liferay Portal 6.2 has been out for a while and you might have read Mate Thurzo’s great blog entries about the topic, yet I think it is also worth the time to talk about a utility framework we developed together with but not only for staging!

The Background Task framework allows the developers to create and manage any kind of tasks that need to be run in a separated thread without the need of knowing anything about JAVA threading or concurrency. Behind the scenes it uses the robust messaging framework of Liferay that does the trick for us, because its message listener invocation mechanism provides the multi-threaded execution for all the tasks. With the help of this new framework we are able to make the export/import processes asynchronous, while we also have the option to continuously monitor the running tasks and check their results.

 

Let’s see how it works

Behind the framework there is a new entity called BackgroundTask which stores all the information the task execution requires, like:

  • servletContextNames: the servlet context names to get the ClassLoaders where the task executor class can be found. If it is null then the default portal class loader is used to determine the task executor class.
  • taskExecutorClassName: the name of a class implements the BackgroundTaskExecutor interface
  • taskContextMap: all the context variables the task execution requires, stored in JSON format
  • completed: a simple flag shows whether the task execution is completed or not
  • status: the current status of the task execution, the available status constants are in the BackgroundTaskConstants class.
  • attachments: you can add FileEntry type objects as attachments for later usage. This feature uses the Portlet File Repository framework to store the entries in a separate folder for each and every BackgroundTask entity.

 

As you can see the entity doesn’t know anything about the business logic we would like to implement, but it has the reference to a class that implements it. The BackgroundTaskExecutor interface has the following methods:

  • BackgroundTaskResult execute(BackgroundTask backgroundTask)

This one is pretty straightforward. You can implement the business logic in this method, but the interesting part is the return value. A BackgroundTaskResult object is a int status - String statusMessage pair that stores information about the result of the task execution.

  • BackgroundTaskStatusMessageTranslator getBackgroundTaskStatusMessageTranslator()

Another powerful feature of the API is the option to send and consume status messages during the execution of the task. The only thing we have to do is to send a message object on the Liferay Message Bus to a specific destination called "liferay/background_task_status" and put the current background task id to the message with key “backgroundTaskId”.

  • String handleException(BackgroundTask backgroundTask, Exception e)

This method is called when a generic Exception is thrown during the task execution. The return value of the method is the status message of the BackgroundTaskResult of the current execution.

  • boolean isSerial()

If this method returns true it means only one task of a given type can run parallel and all the newly created tasks get queued.

 

Examples

After the theory let’s see some examples how to use the framework in some basic scenarios.

 

Implement the task executor:

public class MyBackgroundTaskExecutor extends BaseBackgroundTaskExecutor {  
    public MyBackgroundTaskExecutor() {
        setBackgroundTaskStatusMessageTranslator(
            new MyBackgroundTaskStatusMessageTranslator ());
        setSerial(true);
    }

    @Override
    public BackgroundTaskResult execute(BackgroundTask backgroundTask)
        throws Exception {

        Map taskContextMap = backgroundTask.getTaskContextMap();

        // do your business logic here
    }
}

 

Create a new BackgroundTask object

BackgroundTaskLocalServiceUtil.addBackgroundTask(
    userId, sourceGroupId, StringPool.BLANK, null,
    MyBackgroundTaskExecutor.class, taskContextMap,
    serviceContext);

 

Send status messages:

Message message = new Message();

// Background task id needs to be passed

message.put("backgroundTaskId", BackgroundTaskThreadLocal.getBackgroundTaskId());

// Pass all the necessary attributes here

// Send it over the built-in Message Bus to the background task status
// destination

MessageBusUtil.sendMessage(DestinationNames.BACKGROUND_TASK_STATUS, message);

 

Process the message with a custom message translator:

public class MyBackgroundTaskStatusMessageTranslator 
    implements BackgroundTaskStatusMessageTranslator {

    @Override public void translate(
        BackgroundTaskStatus backgroundTaskStatus, Message message) {

        backgroundTaskStatus.setAttribute(
            "xy", doTranslate(message.getString("xy"));

     }
}

 

Get the BackgroundTaskStatus of an existing task

BackgroundTaskStatus backgroundTaskStatus =
    BackgroundTaskStatusRegistryUtil.getBackgroundTaskStatus(
        backgroundTaskId);

System.out.println(backgroundTaskStatus.getAttribute("xy"));

 

Finally I would like to encourage everyone to use this powerful, but still simple framework to run asynchronous tasks, or to put long running jobs like an import process to the background. It has a lot of potential and as you can see it is really easy use to resolve complex problems while you have the freedom to do anything you want.

Showing 1 result.