Jobs
Skyve provides two different mechanisms for executing tasks in the background, jobs and background tasks. Jobs can be scheduled to run at a specific time or on a recurring schedule. Background tasks are used to run tasks in the background while the user continues to work in the application.
Jobs
A Job represents an asynchronous process that can be scheduled or run ad-hoc. Jobs are registered against a module and available to be scheduled through the user interface. The are typically long-running tasks that can be scheduled to run at a specific time or on a recurring schedule.
Jobs are declared in the module.xml
file in the <jobs>
section.
Job declaration includes logical name, displayName and className.
The className nominates the specific class file to be executed.
Once jobs have been declared, they are available to be scheduled at run-time via the admin module job scheduler function.
The admin module provides comprehensive job scheduling functionality, including assignment of the user under whose privileges the Job will be executed.
Scheduling Jobs from the admin module requires the JobMaintainer role.
Although job logging can be turned off for a Job, each run is generally logged.
Job Classes
Job classes must extend the org.skyve.job.Job
abstract class. Custom job code is located in the execute()
method.
public class ProcessCommunicationForTagJob extends Job {
private static final long serialVersionUID = 6282346785863992703L;
@Override
public String cancel() {
return null;
}
@Override
public void execute() throws Exception {
List<String> log = getLog();
Communication communication = (Communication) getBean();
The Skyve Job method getLog()
retrieves the corresponding job log object, allowing the developer to log job activity (viewable for a suitably privileged user in the Job view in the admin module).
The Skyve Job method getBean()
returns the corresponding bean instance where applicable (where a job has been instantiated
within a bean context or action).
Jobs can be scheduled in action or Bizlet code using the JobScheduler class.
/**
* Kick off the annual returns job
*/
@Override
public ServerSideActionResult<GrowerSearchCriteria> execute(GrowerSearchCriteria bean, WebContext WebContext) throws Exception {
User user = CORE.getPersistence().getUser();
Customer customer = user.getCustomer();
Module module = customer.getModule(Grower.MODULE_NAME);
JobMetaData job = module.getJob("jAnnualReturns");
EXT.getJobScheduler().runOneShotJob(job, bean, user);
webContext.growl(MessageSeverity.info, "The generation job has commenced.");
return new ServerSideActionResult<>(bean);
}
Example action class code to run a one-shot Job
In the above example, the call EXT.getJobScheduler().runOneShotJob
will schedule the job to run passing in the bean named search under the permissions of the current user.
As Jobs are run within the context of a user so that Skyve’s embedded comprehensive security model can be enforced.
Developers must consider whether a user context will have sufficient privileges for the Job to be executed.
Other Job Subclasses
While most jobs can be defined by extending org.skyve.job.Job
, there are two useful Job subclasses available which can be useful in certain scenarios:
CancellableJob
Extending org.skyve.job.CancellableJob
provides the job with an isCancelled()
method. This is useful for long running jobs which can be interrupted. This can be called in a for
or while
loop for example by checking during each iteration which allows the job to complete early:
if (isCancelled()) {
getLog().add("Job cancelled...");
return;
}
IteratingJob
Extending org.skyve.job.IteratingJob
is useful when the job is primarily responsible for processing a collection of the same type records. This defines two abstact methods which much be extended to tell the job which elements to iterate over (getElements()
) and what operation to perform on each element (operation()
). When the job executes, it will keep track of updating the progress percentage of the job and logging how many successful and unsucsessful records were processed.
An example of using this job is shown below:
import org.skyve.job.IteratingJob;
public class ExpirePasswordJob extends IteratingJob<UserExtension> {
@Override
protected Collection<UserExtension> getElements() {
// select all users to update
return CORE.getPersistence().newDocumentQuery(User.MODULE_NAME, User.DOCUMENT_NAME)
.beanResults();
}
@Override
protected void operation(UserExtension element) throws Exception {
// perform an operation on each user
element.setPasswordExpired(Boolean.TRUE);
// save the changes
CORE.getPersistence().save(element);
}
}
Job Transactions
Unless specified by the developer, a job will run in a single transaction and roll-back if an exception is thrown. This may be suitable especially for jobs dealing with small numbers of beans.
However, for jobs interacting with a large (or potentially large) number of beans, it may be useful to commit after each interaction, and possibly evict the cached bean to free resources.
public class DeleteTaggedRecordsJob extends Job {
private static final long serialVersionUID = 6282346785863992703L;
@Override
public String cancel() {
return null;
}
@Override
public void execute() throws Exception {
List<String> log = getLog();
Tag tag = (Tag) getBean();
log.add("Started Delete Tagged contacts at " + new Date());
Persistence pers = CORE.getPersistence();
List<Bean> beans = TagBizlet.getTaggedItemsForDocument(tag, Contact.MODULE_NAME, Contact.DOCUMENT_NAME);
int size = beans.size();
int processed = 0;
Iterator<Bean> it = beans.iterator();
while (it.hasNext()) {
Contact c = (Contact) it.next(); //get the contact
String bizKey = c.getBizKey(); // remember details for logging below
EXT.untag(tag.getBizId(), c); // remove the contact from the Tag set
pers.delete(c); // delete the contact
pers.commit(false); // commit the transaction
pers.evictCached(c); // remove the deleted contact from the cache to free up resources
pers.begin(); // start a new transaction for the next iteration
log.add("The contact " + bizKey + " was deleted."); //log the result
processed++; // increment the counter
setPercentComplete((int) (((float) processed) / ((float) size) * 100F));
}
setPercentComplete(100);
log.add("Finished Delete Tagged contacts at " + new Date() + ". " + processed + " contacts were deleted.");
}
}
The above example shows a number of common patterns for Jobs.
- Firstly, the job uses the Tag concept, allowing the user to select which data will be affected at runtime.
- The job uses the iterator to safeguard against concurrent modification problems with the
List<Bean>
collection. - The job commits each change, evicts any cached bean and then starts (
begin()
) a new transaction to free resources as it goes.
With this approach, should the job fail, deletions up to the point of failure are committed and won’t be rolled back.
Developers should consider carefully implications of jobs which may process large numbers of beans and decide on an approach which best suits their needs.
For further examples, review the following classes in the Skyve admin module:
- admin.Communication.ProcessCommunicationForTagJob
- admin.DataMaintenance.BackupJob
- admin.DataMaintenance.RefreshDocumentTuplesJob
- admin.DataMaintenance.TruncateAuditLogJob
- admin.Tag.PerformDocumentActionForTagJob
- admin.UserList.BulkUserCreationJob
Logging
The Job class provides the List<String> log
for developer logging.
The log is viewable while the job is running or after the job has completed.
To view the log, go to the admin->Jobs menu item and view the list of jobs (either running or completed).
Then click to view the job details and log.
Background Tasks
Background tasks allow the user to continue working in the application while a task is running in the background. This can be used for short-running tasks need to continue to run in the background, but do not need to be scheduled like a Job. Some examples of this are sending an email, or preparing a report.
A ViewBackgroundTask
is a short-running asynchronous process that can be kicked off via WebContext (no percent complete or interrupt method). It is applicable to the UI it is initiated from, and has access to and the ability to control the conversation caching in its execution via cacheConversation()
.
It continues to use the Persistence
instance from the conversation with all the state as if it was another user gesture (same database cache, same local UI mutations).
It’s execute()
method takes the current contextual bean and is never null - ie it always must have a UI context (the record that initiated the task).
The persistence is set as async for the thread so that aysnc timeouts are used during its execution similar to Jobs (configured in the application .json properties file).
ViewBackgroundTask
Background tasks must extend the org.skyve.job.ViewBackgroundTask
abstract class. The application logic to run in the background is placed in the execute()
method.
public class MonthlyReportTask extends ViewBackgroundTask<MonthlyReport> {
@Override
public void execute(MonthlyReport bean) throws Exception {
// generate and email the report
// ...
}
}
There are a couple of different methods of running the background task. From an action or Bizlet method with a webContext
, there is a convenience background()
method to run the task:
@Override
public ServerSideActionResult<MonthlyReport> execute(MonthlyReport bean, WebContext webContext) throws Exception {
// initiate task
webContext.growl(MessageSeverity.info, String
.format("A report job has been initiated. You will receive an email once the report is successfully generated."));
webContext.background(MonthlyReportTask.class);
return new ServerSideActionResult<>(bean);
}
Alternatively, if you have access to the webId
of the conversation, a task can be started in a similar method to immediately executing a job:
EXT.getJobScheduler().runBackgroundTask(MonthlyReportTask.class, CORE.getUser(), webContext.getWebId());