Aspire has two features that work together in scheduling requests. A "request" in Aspire's parlance is a unit of work or at times called a task. So when you schedule a request, you are actually scheduling tasks at a given interval.
The scheduler that is available in Aspire is a very basic scheduler with minimal but sufficient-for-the-purpose capabilities. Using this scheduler you can schedule a task (a piece of java code) for every n number of timer ticks. A timer tick is defined as a certain number of seconds.
For a task to be invoked by a scheduler it had to be registered. This is where the initialization support in Aspire come into play. Initializers in Aspire allow you to run code at the start up time. You can read the document on initialization to learn more about the interfaces.
In this write up we will design an initializer that will allow us to declarativelys schedule a task (or request). To begin this design let us visualize how to use such a facility.
Aspire.startup.initializers=ScheduleTask1,ScheduleTask2
The above line indicates to Aspire that "ScheduleTask1" and "ScheduleTask2" are two initializers whose "initialize" methods needs to be called. Let us see how these tasks will look like in the config file.
request.ScheduleTask1.classname=com.ai.scheduler.RequestScheduler
request.ScheduleTask1.requestName=(your-request-name-1)
request.ScheduleTask1.howmanyTicks=20
request.ScheduleTask2.classname=com.ai.scheduler.RequestScheduler
request.ScheduleTask2.requestName=(your-request-name-2)
request.ScheduleTask2.howmanyTicks=20
Let me spend some time explaining the above definitions. What you see above are two identical definitions. So it is sufficient to discuss just one of them, the first one. The first of the three lines is pointing to a java class name which has the initializer method in it. As part of this initialization, the "RequestScheduler" will register a class with the Scheduler so that it gets invoked every 20 timer ticks.
Let me briefly digress and show you how the (request-name-1) is defined
request.(request-name-1).classname=com.ai.db.DBRequestExecutor2
request.(request-name-1).db=(my-database-alias)
request.(request-name-1).query_type=update
request.(request-name-1).stmt=update (some-table) where (where-clause)
package com.ai.scheduler;
import com.ai.application.interfaces.*;
import com.ai.application.utils.*;
public class RequestScheduler implements IApplicationInitializer
, IInitializable
, ISingleThreaded
, IScheduleTask
{
public String m_initializerName = null;
public String m_requestNameToSchedule = null;
public int m_timerTicks = 0;
//*************************************************************************************
//Implementing the IApplicationInitializer contract
//*************************************************************************************
public boolean initialize(IConfig cfg, ILog log, IFactory factory)
{
try
{
IScheduler scheduler =
(IScheduler)factory.getObject(IScheduler.GET_SCHEDULER_REQUEST,null);
scheduler.schedule(this,new com.ai.scheduler.BasicScheduleTime(m_timerTicks));
return true;
}
catch(com.ai.application.interfaces.RequestExecutionException x)
{
AppObjects.log("Error: Initialization exception for initializer:" + m_initializerName, x);
return false;
}
catch(SchedulerException x)
{
AppObjects.log("Error: Scheduler exception for:" + m_initializerName, x);
return false;
}
}
//*************************************************************************************
//Implementing IInitializable to know the context and cache the request name to schedule
//*************************************************************************************
public void initialize(String requestName)
{
try
{
m_initializerName = requestName;
m_requestNameToSchedule = AppObjects.getValue(requestName + ".requestNameToSchedule");
String timerTicks = AppObjects.getValue(requestName + ".howmanyTimerTicks");
m_timerTicks = Integer.parseInt(timerTicks);
}
catch(com.ai.application.interfaces.ConfigException x)
{
AppObjects.log("Error:Could not initialize the task for initializer:" + requestName, x);
}
}
//*************************************************************************************
//Implementing IScheduleTask
//Callback from the scheduler
//*************************************************************************************
public boolean execute()
{
try
{
AppObjects.getObject(m_requestNameToSchedule,null);
return true;
}
catch(RequestExecutionException x)
{
AppObjects.log("Error:Error executing " + m_requestNameToSchedule,x);
return false;
}
}
}
The Request scheduler class above is performing multiple roles and fullfilling multiple contracts in the process. The primary role of the RequestScheduler is to be an "IApplicationInitializer" and implement the initialization interface at the startup. As part of the initialization it is necessary to read the additional arguments to this class. Unfortunately the interface to the initializer did not support this. I have extended this interface in future releases to have the request name passed in as the 4th argument to this method.
For now I needed a work around to read the additional arguments that need to be read. The factory interface through which these initializers are loaded supports a "separate" interface called "IInitializable()" which will get called when the class is instantiated. The method that gets invoked is the
public void initialize(String requestname)
By implementing this interface we have an opportunity to cache this name in the class and also the additional parameters that are hitched up to this request name. But as soon as we save local variables in the class name, then the class can not be singleton and serve multiple requests. As a fix we have used the interface tag called ISingleThreaded which will force the multiple instantiation of this class one for each scheduled task.
Although the above multi instantiation is a drawback usually, in this case it turns out to be not bad. Here is the reason. When we proceed to create an IScheduleTask object and pass it to the scheduler we need to instantiate individual task objects. If we role up that task into the RequestScheduler and have it behave like a scheduled task as well, we can avoid the original penalty.
What follows is a complete listing of each of the interfaces
What follows is a listing of the involved interfaces
package com.ai.application.interfaces;
public interface IApplicationInitializer
{
public boolean initialize(IConfig cfg, ILog log, IFactory factory);
}
package com.ai.scheduler;
public interface IScheduleTask
{
public boolean execute();
}
package com.ai.application.interfaces;
public interface IInitializable
{
public void initialize(String requestName);
}
There is a small problem with IApplicationInitializer. Its initialize method does not pass in the requestname. This puts a damper on the implementation as the implementation does not know in what context it is invoked. If it does not know the request name through which it is invoked, it won't know how to read the additional arguments. I have corrected this problem in the next release of this interface. Here is the code for it.
package com.ai.application.interfaces;
public interface IApplicationInitializer1
{
public boolean initialize(IConfig cfg, ILog log, IFactory factory, String requestName);
}
Let us see how we can rewrite the above code using this interface instead
package com.ai.scheduler;
import com.ai.application.interfaces.*;
import com.ai.application.utils.*;
public class RequestScheduler implements IApplicationInitializer
, ISingleThreaded
, IScheduleTask
{
public String m_initializerName = null;
public String m_requestNameToSchedule = null;
public int m_timerTicks = 0;
//*************************************************************************************
//Implementing the IApplicationInitializer contract
//*************************************************************************************
public boolean initialize(IConfig cfg, ILog log, IFactory factory, String requestName)
{
try
{
m_initializerName = requestName;
//Read the additional arguments
m_requestNameToSchedule = AppObjects.getValue(requestName + ".requestNameToSchedule");
String timerTicks = AppObjects.getValue(requestName + ".howmanyTimerTicks");
m_timerTicks = Integer.parseInt(timerTicks);
//Call the scheduler
IScheduler scheduler =
(IScheduler)factory.getObject(IScheduler.GET_SCHEDULER_REQUEST,null);
//Schedule the task
scheduler.schedule(this,new com.ai.scheduler.BasicScheduleTime(m_timerTicks));
return true;
}
catch(com.ai.application.interfaces.RequestExecutionException x)
{
AppObjects.log("Error: Initialization exception for initializer:" + m_initializerName, x);
return false;
}
catch(SchedulerException x)
{
AppObjects.log("Error: Scheduler exception for:" + m_initializerName, x);
return false;
}
}
//*************************************************************************************
//Implementing IScheduleTask
//Callback from the scheduler
//*************************************************************************************
public boolean execute()
{
try
{
AppObjects.getObject(m_requestNameToSchedule,null);
return true;
}
catch(RequestExecutionException x)
{
AppObjects.log("Error:Error executing " + m_requestNameToSchedule,x);
return false;
}
}
}
The first code should work on all builds. The second set of code that uses the IApplicationInitializer1 will only work with build 19 or above. This build is not released at the time of this note. But should be released in the next two weeks or so.