Overview of workmanagers

One usecase that can pop up during development is that a long running task needs to be executed with an ADF action component. Usually, these types of operations are executed inside the action component’s action listener, but in this case due to the fact that the task may take longer to execute than the preconfigured request timeout, the operation might fail and the application remains frozen for the duration of the operation.

One real world example of this use case we encountered at virtual7 was when we had to do an indexing, of several thousand documents hosted in a WebCenter Content Server, in an ElasticSearch instance. The client wanted an Administration Console application from where it could trigger the indexing operation. Because the operation took longer than the server’s request timeout interval the operation would fail and the documents would not be indexed in ElasticSearch.

The solution to this issue is configuring a workmanager on the WebLogic server where the application is running. Workmanagers execute the task asynchronously and allow the request that started the operation to complete and the application to continue the execution. In addition, a work listener can be configured that defines handlers for various stages in the work execution.

More information on workmanagers can be found in the oracle documentation at:

https://docs.oracle.com/cd/E11035_01/wls100/config_wls/self_tuned.html

 

Registering a workmanager

Workmanagers are defined in the WebLogic console under Environment -> Workmanagers

register-weblogic0

To create a new workmanager press new and follow the wizard. For the purpose of this article, all the configurations will be left as default.

register-weblogic1

Give the workmanager a name (TutorialWM in this case) and target it to the server on which the application will be running (in the Integrated WL it’s the Admin Server).

register-weblogic2

 

Using the workmanager in an application

After creating the workmanager in the WebLogic console we can use it from inside an application to delegate long running tasks. The first step is to create a reference to it in the application’s web.xml deployment descriptor.

<resource-ref>
    <res-ref-name>wm/TutorialWM</res-ref-name>
    <res-type>commonj.work.WorkManager</res-type>
    <res-auth>Container</res-auth>
    <res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>

In order to create the classes that will use the workmanager we need to add a library to the ViewController project: Weblogic 12.1 remote-Client.

library-ref

The sample application we are going to create consists of 3 java classes and a managed bean:

  1. LongRunningTask.java
  2. WorkInstance.java
  3. MyWorkListener.java
  4. MyBackingBean.java

 

LongRunningTask

This class represents an operation that will take several minutes to complete. To illustrate this we chose a simple implementation:

public void startTask() {
        System.out.println("Starting long running task.");
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Task running for " + (i + 1) + " seconds.");
        }
        System.out.println("Task finished.");
    }
}

WorkInstance

This class represents the work object that will be passed to the workmanager for processing and it must implement the commonj.work.Work interface. Here inside the run() method we trigger the execution of the long running task:

public class WorkInstance implements Work {
    private LongRunningTask task = new LongRunningTask();

    public LongRunningTask getTask() {
        return task;
    }

    @Override
    public void release() {

    }

    @Override
    public boolean isDaemon() {
        return false;
    }

    @Override
    public void run() {
        try {
            new Callable<LongRunningTask>() {
                @Override
                public LongRunningTask call() {
                    task.startTask();
                    return task;
                }
            }.call();
        } catch (Exception e) {
            System.out.println(e.getStackTrace());
        }
    }
}

MyWorkListener

Here we implement the handlers for the work events fired by the work manager. We can use this class to write to the database or send servlet requests. A note on this class is that it does not run within the application context, so accessing objects like the session scope or the ADF context will result in errors.

public class MyWorkListener implements WorkListener {

    @Override
    public void workAccepted(WorkEvent workEvent) {
        // TODO Implement this method
    }

    @Override
    public void workRejected(WorkEvent workEvent) {
        // TODO Implement this method
    }

    @Override
    public void workStarted(WorkEvent workEvent) {
        System.out.println("Long running task started event handled.");
    }

    @Override
    public void workCompleted(WorkEvent workEvent) {
        System.out.println("Long running task completed event handled.");
    }
}

MyBackingBean

Is a backingBean scoped bean where we implement the action listener that trigger the long running operation and a popup fetch listener to illustrate that the application execution can continue while the work is in progress.

To use the workmanager we have to lookup the workmanager using JNDI, create a work instance and pass it on to the workmanager along with the listener instance where we want to handle the work events.

public class MyBackingBean {
    public void onStartTask(ActionEvent actionEvent) {
        try {
            InitialContext ctx = new InitialContext();
            WorkManager workManager = (WorkManager) ctx.lookup("java:/comp/env/wm/TutorialWM");

            Work work = new WorkInstance();
            workManager.schedule(work, new MyWorkListener());

        } catch (NamingException ne) {
            ne.printStackTrace();
        } catch (WorkException we) {
            we.printStackTrace();
        }
    }

    public void onPopupFetch(PopupFetchEvent popupFetchEvent) {
        System.out.println("Popup listener called. this handler is running asynchronously with the long task");
    }
}

Running the application

The application has a simple interface with 2 buttons, one triggers the long running task and the other opens a message popup.

interface

When clicking the “Start Long Running Task” button we can see in the console that the startTask() method of the LongRunningTask is called and also we can see that the workStarted and the workCompleted events were handled according to the implementation from MyWorkListener:

run1

To demonstrate that the work is executed asynchronously we can click the “Show Message” button while the work is executing to display a popup:

show-popup

In the console we see that the popup listener was called at the same time as the long running task is executing.

run2