Job Module

Last modified by Vincent Massol on 2017/07/14 18:00

cogA set of APIs to easily communicate with an asynchronous task
TypeJAR
Developed by

XWiki Development Team

Rating
Rate!
0 Votes
LicenseGNU Lesser General Public License 2.1
Bundled With

XWiki Enterprise, XWiki Enterprise Manager

Compatibility

Since 4.0 Milestone 1

Description

The goal of this module is to make easier to allow two ways communication with a task executed in a background thread.

Minimal job provide:

  • progress information
  • live log
  • Java bean based question/answer system

and can be extended as long as the client of the API know the extended interface.

Use an existing job

The common way to use a job is through org.xwiki.job.JobExecutor component since 6.1 and org.xwiki.job.JobManager component before 6.1.

org.xwiki.job.JobManager execute all jobs one by one in a unique thread. For other use case you can either create you own JobManager component or use org.xwiki.job.JobExecutor starting with XWiki 6.1.
Request jobRequest = new DefaultRequest();

// Indicate an id to allow you to access the status of this unique task, the id is required to save/retrieve the status of the job
jobRequest.setId("taskid");
// Allow the job to ask questions during its executing (false by default)
jobRequest.setInteractive(true);
// [since 5.4] Indicate if the job should log informations about what is going on (to display it during the process for example, true by default)
jobRequest.setVerbose(true);
// Put some custom configuration
jobRequest.setProperty("some custom jobtype related parameter", "value");

// Lookup and add Job with role hint "jobtype" to the queue and return right away
Job job = jobExecutor.execute("jobtype", request);

// Wait until the job is finished
job.join();

Create a reusable job

To register a new job that anyone can then use in JobExecutor (or JobManager in older versions) you need to register a component with role org.xwiki.job.Job and a unique role hint.

Since 6.1 a Job can also implement org.xwiki.job.GroupedJob to indicate which group this job is part of. In a group jobs are executed in the same thread one by one by JobExecutor.

Since 7.4 a public org.xwiki.job.AbstractJob is provided to help implementing a Job. An org.xwiki.job.internal.AbstractJob class already existed since 5.0 but it was internal.
org.xwiki.job.AbstractJob is dealing with a lot of plumbing, among other things:

  • it automatically store anything logged (the standard slf4j way) in the status
  • it automatically listen to events generated trough org.xwiki.job.event.status.JobProgressManager component and update the progress in the job status
  • it deals with question/answer complexity (waiting for an answer, etc.)
  • it automatically store the job status asynchronously when the job is finished
  • it log and generate related events when the job start and when the job ends
  • it implements #join()
  • it maintain a tree of jobs in the context

Execute custom job

JobExecutor provide an API to execute a custom instance of Job.

jobExecutor.execute(job);

Starting with 5.1, it's also possible to create a custom Job and add it to the queue using JobManager#addJob(Job job).

Notify about progress

It's possible to notify about progression of a task by sending progression event that are then received by the job (or the jobs) to update its progress informations.

In Velocity scripts

[Since 7.1]

// Push 2 sub steps
$services.progress.pushLevel(2)

// First step
$services.progress.startStep('First step')
#firstStep()
$services.progress.endStep()

// Second step
$services.progress.startStep('Second step')
#secondStep()
$services.progress.endStep()

$services.progress.popLevel()

In Java

Since 7.1

class MyComponent
{
 @Inject
 private JobProgressManager progressManager;

 void foo()
 {
   // Push 2 sub steps
   progressManager.pushLevelProgress(2, this);

   try {
     // First step
     this.progressManager.startStep(this, "First step");
      firstStep();
     this.progressManager.endStep(this);

     // Second step
     this.progressManager.startStep(this, "Second step");
      secondStep();
   } finally {
     // Done, go back to parent progress level
     this.progressManager.popLevelProgress(this);
   }
 }

 void firstStep()
 {
    progressManager.call(new Callable() {
       // First sub-step
       progressManager.startStep(this);
        firstSubStep();
        progressManager.sendStep(this);

       // Second sub-step
       progressManager.startStep(this);
        secondSubStep();
     },
   2, this);
 }
}

Between 6.1 and 7.0:

class MyComponent
{
 @Inject
 private JobProgressManager progressManager;

 void foo()
 {
   // Push 2 sub steps
   progressManager.pushLevelProgress(2, this);

   try {
      firstStep();

     // First step done
     this.progressManager.stepPropress(this);

      secondStep();
   } finally {
     // Done, go back to parent progress level
     this.progressManager.popLevelProgress(this);
   }
 }

 void firstStep()
 {
   // Since 7.1M1 Callable support has been added
   progressManager.call(new Callable() {
        firstSubStep();

       // First step done
       progressManager.stepPropress(this);

        secondSubStep();
     },
   2, this);
 }
}

Before 6.1:

class MyComponent
{
 @Inject
 private ObservationManager observationManager;

 protected void notifyPushLevelProgress(int steps)
 {
     this.observationManager.notify(new PushLevelProgressEvent(steps), this);
 }

 protected void notifyStepPropress()
 {
     this.observationManager.notify(new StepProgressEvent(), this);
 }

 protected void notifyPopLevelProgress()
 {
     this.observationManager.notify(new PopLevelProgressEvent(), this);
 }

 void foo()
 {
   // Push 2 sub steps
   notifyPushLevelProgress(2, this);

   try {
      firstStep();

     // First step done
     notifyStepPropress(this);

      secondStep();
   } finally {
     // Done, go back to parent progress level
     notifyPopLevelProgress(this);
   }
 }
}

Job status

Each job expose a live status which will also be automatically serialized when the job is finished (if the job has an id).

Progress

The job progress provide data to display live information about the job like a progress bar.

Since 7.1 it also provide a tree of steps with an associated message and elapsed time for each one.

Events

The following notification events are fired during the execution of a job whose status extends the default job status:

  • JobStartedEvent: fired when the job starts. The event is send with the following parameters:
    • source: the job that has started
    • data: null
  • QuestionAskedEvent: fired when an interactive job asks a question. The event listeners have the chance to answer the question before it reaches the user. The event is send with the following parameters:
    • source: the status of the job that asked the question, which can be used to access the question
    • data: null
  • QuestionAnsweredEvent: Event fired after a question raised by an interactive job is answered. The event is send with the following parameters:
    • source: the job status, that can be used to access the question
    • data: null
  • JobFinishedEvent: fired when the job finishes. The event is send with the following parameters:
    • source: the job that has finished
    • data: a Throwable if the job execution failed or null otherwise
  • [since 8.1] JobFinishingEvent: fired just after a job is done and we start to save status etc. Give a chance to listeners to do action as part of the job execution before it's "closed". The event is send with the following parameters:
    • source: the job that has finished
    • data: a Throwable if the job execution failed or null otherwise

Script Service

Starting with XWiki 7.2M1 there is a script service available to retrieve:

  • the status of a specified job
  • the current job from a specified group and its status
## The job id is a list of strings which are domain specific.
#set ($farmDistributionJobStatus = $services.job.getJobStatus(['distribution']))
#set ($solrIndexerJobStatus = $services.job.getJobStatus(['solr', 'indexer']))

#set ($currentJobStatus = $services.job.getCurrentJobStatus(['my', 'job', 'group']))
The current job is $currentJobStatus.state

Configuration

#-# [Since 4.0M1]
#-# The folder containing job executing status.
#-# The default is {environment.permanentDirectory}/jobs/
# job.statusFolder=/var/lib/xwiki/data/jobs/

#-# [Since 7.2M2]
#-# The maximum number of entries to put in the job status cache.
#-# The default is 50.
# job.statusCacheSize=50

Related Links

Tags:
Created by Thomas Mortagne on 2012/04/03 11:14
    

Get Connected