Introduction
In one of our projects, we are creating an integration Java application running on either WebSphere Liberty or WebSphere Application Server full profile. This application has two scheduled components, which is completely normal. But, when we came to the part, which runtime will be used, we were stuck (it was unclear at that moment of the project).
The problem is pretty simple, the scheduler must be implemented on a different way when running on Liberty or full profile. We wanted to avoid creating multiple scheduler implementations either as a seperated component (wrapped in a JAR file or something) or application version.
Struggle with WebSphere runtimes
The problem with Liberty and full profile, that both are implementing a scheduler on a different ways to schedule a particular job. WebSphere Liberty leverages the javax.enterprise.concurrent.ManagedScheduledExecutorService
which is awesome, but the full profile unfortunately does not support it.
The full profile still (*sigh*) using the old commonj.timers.TimerManager
implementation, which is fair enough, but better put it back to the Historical Museum if we can.
So these are the differences between the runtime environments, and this is making difficult to us a bit hard.
Solution: abstract scheduler
We created an abstract class, hiding the implementation from the child classes. Also, this class has two public abstract methods: getInterval()
and run()
. These methods can be used by the child classes later.
AbstractScheduler#getInterval()
This method has only one purpose, define the frequency of the scheduled task. In other words, this method will return a long type number. In our case, this return only a positive number.
Example from the child class:
@Override
public long getInterval() {
return 5;
}
AbstractScheduler#run()
This method can declare the actual task, which will be executed by the scheduler.
Example from the child class:
@Override
public void run() {
… select some values from a database and update those values in
a different system using REST calls.
Or whatever...
}
Okay, I see, get me the implementation
Okay, okay. I’m done with intros. Let’s get to the implementation. Let’s create the abstract class with the scheduler implementation, then we will create a child class extending the abstract class.
public abstract class AbstractScheduler implements Serializable, TimerListener {
private static final long serialVersionUID = -9028396825843511410L;
}
So, right now, we have an abstract class, which implements the Serializable
and TimerListener
interfaces. The class also has a generated serial version UID. The fun part now comes: add some private fields:
private TimerManager tm = null;
private Timer timer = null;
private ScheduledFuture<?> scheduledFuture = null;
So, we created 3 fields so far. The TimerManager
will hold a reference of the TimerManager
, when running on full profile. The Timer
object will hold a reference of the scheduled task itself, making us able to create a method, which can terminate the earlier scheduled task (see later). Finally, the ScheduledFuture
will hold a reference for the scheduled task, when using the ManagedScheduledExecutorService
implementation.
Add methods
Now we need to create 5 methods (2 public abstract, 1 private, 1 public and 1 public final). These are the following (in alphabet order):
public final void cancelTask() {}
public abstract long getInterval();
public void initialize() {}
public abstract void run();
private Runnable runRunnable() {}
Please have a look on the following 5 mini chapters to get an understanding why those methods are created.
AbstractScheduler#cancelTask()
This method will help you successfully terminate an earlier scheduled task. This method will be universal, because based on the runtime environment, it will use the proper interfaces and objects, to cancel any kind of task. Useful when you have some listener (ServletContextListener
, etc.) classes where you are handling the startup/shutdown scenarios.
AbstractScheduler#getInterval()
This is I mentioned before, determines the frequency of the scheduled task needs to be executed.
AbstractScheduler#initialize()
This method is the main actor of this blog entry. This is where we are going to implement the runtime environment specific scheduler (see later).
AbstractScheduler#run()
This was also mentioned earlier, this method will be implemented by the child classes and define the exact logic, which will be executed on a periodic way by the scheduler.
AbstractScheduler#runRunnable()
This is required, a private method to be able to execute the run()
method when going with Liberty runtime. Consider it as a wrapper method.
AbstractScheduler#initialize()
Haha! Still reading? You are really curious. So, we did not (I mean in our project) want to make a decision about the platform using some voodoo magic, so we put a key=value pair in the app’s config file, where the installer will set the proper value. I mean, a property which tells the application that we are running on Liberty or not. For this example, we are going to use a simple boolean
variable (true
means Liberty, false
means Full Profile).
public void initialize() {
boolean isLiberty = true;
if(isLiberty) {
// We are running on liberty
ManagedScheduledExecutorService executor = (ManagedScheduledExecutorService) new InitialContext().lookup("java:comp/env/tm/default");
scheduledFuture = executor.scheduleAtFixedRate(runRunnable(), 0, getInterval(), TimeUnit.MINUTES);
} else {
// We are running on full profile
tm = (TimerManager) new InitialContext().lookup("java:comp/env/tm/default");
timer = tm.scheduleAtFixedRate(this, 0, getInterval() * 60 * 1000);
}
}
So, simple is that, isn’t it? What is the trick here? The full profile does not want to load the ManagedScheduledExecutorService
implementation until the first call on that object is made. So, technically, the implementation completely can be missing on a full profile, this still works. And vica versa in Liberty. Awesome!
Please notice, that TimerManager
is using the time value in milliseconds, but the ManagedScheduledExecutorService
can be used on different ways (i.e.: TimeUnit.HOURS
, TimeUnit.SECONDS
etc.).
AbstractScheduler#cancelTask()
This method as I mentioned earlier is required to be able to cancel/demolish/stop the task scheduled before. This is, where more of the fields are being used. Let’s have a look.
boolean isLiberty = true;
if (isLiberty) {
if (!scheduledFuture.isCancelled()) {
scheduledFuture.cancel(false);
}
} else {
timer.cancel();
}
The false
attribute at the scheduledFuture.cancel()
method will order the cancel()
method to be graceful. So if there are any task which are currently running by the scheduler, the cancel will wait to finish and then, canceling them.
Unfortunately, the Timer
implementation does not have such a graceful kill, so simply kill it.
timerExpired() and run()
Because the AbstractScheduler
implements the TimerListener
interface, it means that a method must be implemented by our class from the TimerManager
. This is timerExpired()
method.
We wanted to hide any implementation specific code from the child objects, so we created a run()
method, which will be used by the timerExpired()
method as well as the runRunnable()
method.
The timerExpired()
is very simple, it will just call the run()
method. So when the TimerManager
is firing, it will execute the timerExpired()
method which calls the run()
method. Method chaining! 🙂
private void timerExpired(Timer arg0) {
run();
}
The runRunnable()
is a different story, because this will be called when running on Liberty and have to use the ManagedScheduledExecutorService
. So technically, the run()
method needs to be executed whatever Scheduler implementation we have.
private Runnable runRunnable() {
final AbstractScheduler aSch = this;
return new Runnable() {
public void run() {
aSch.run();
}
};
}
Puting all those together
So, we are done here. Let me show you the full AbstractScheduler
looks like.
package test;
import java.io.Serializable;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.enterprise.concurrent.ManagedScheduledExecutorService;
import javax.naming.InitialContext;
import commonj.timers.Timer;
import commonj.timers.TimerListener;
import commonj.timers.TimerManager;
public abstract class AbstractScheduler implements TimerListener, Serializable {
private static final long serialVersionUID = -1746069930718614842L;
private TimerManager tm = null;
private Timer timer = null;
private ScheduledFuture<?> scheduledFuture = null;
private boolean isLiberty = true;
@Override
public void timerExpired(Timer arg0) {
// TODO Auto-generated method stub
run();
}
public void initialize() {
try {
if (isLiberty) {
ManagedScheduledExecutorService executor = (ManagedScheduledExecutorService) new InitialContext()
.lookup("java:comp/env/tm/default");
// Executor requires minutes based on the TimeUnit
scheduledFuture = executor.scheduleAtFixedRate(runRunnable(), 0, getInterval(), TimeUnit.MINUTES);
} else {
tm = (TimerManager) new InitialContext().lookup("java:comp/env/tm/default");
// TimerManager requires milliseconds
timer = tm.scheduleAtFixedRate(this, 0, getInterval() * 60 * 1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public abstract long getInterval();
public abstract void run();
private Runnable runRunnable() {
final AbstractScheduler aSch = this;
return new Runnable() {
public void run() {
aSch.run();
}
};
}
public final void cancelTask() {
try {
if (isLiberty) {
if (!scheduledFuture.isCancelled()) {
scheduledFuture.cancel(false);
}
} else {
timer.cancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
And let’s create a child Scheduler
So we have our AbstractScheduler
in place, let’s create a child object (called ChildScheduler
), extending the abstract class and doing the dirty job IRL.
package test;
import java.util.Date;
public class ChildScheduler extends AbstractScheduler {
private static final long serialVersionUID = 848658559170696979L;
@Override
public long getInterval() {
return 1;
}
@Override
public void run() {
Date current = getCurrentDate();
System.out.println(current);
}
private Date getCurrentDate() {
return new Date();
}
}
Result
So, if you put those classes to a J2EE application, and install first on a Liberty profile, the code will use the ManagedScheduledExecutorService
to schedule the getCurrentDate()
method. If you install it to a full profile WAS, it will use the TimerManager
to call the getCurrentDate()
method.
So, I created a dummy servlet which starts the scheduler. The scheduler will iterate twice, and in the meantime, I paused the Thread
for 90 seconds in total, and stop the scheduler. Please, this is an unsupported way to pause a thread, I used for just the demonstration. The log messages are coming from the server. 🙂
package test;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = "/_test")
public class TestController extends HttpServlet {
private static final long serialVersionUID = 8059837220012295812L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Starting child scheduler...");
ChildScheduler cs = new ChildScheduler();
cs.initialize();
System.out.println("Scheduler started...");
try {
System.out.println("Get a new date after 1 minute, after second date, waiting 30 seconds...");
Thread.sleep(90 * 1000);
System.out.println("---------------------------------");
System.out.println("Stopping scheduler...");
cs.cancelTask();
System.out.println("Scheduler stopped.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
WebSphere Liberty log output:
Starting child scheduler...
Scheduler started...
Tue May 16 22:09:41 CEST 2017
Waiting 90 seconds...
Tue May 16 22:10:41 CEST 2017
---------------------------------
Stopping scheduler...
Scheduler stopped.
And the same code on Full Profile:
[05/16/17 22:14:19:397 EST] 0000001d SystemOut O Starting child scheduler
[05/16/17 22:14:19:397 EST] 0000001d SystemOut O Scheduler started...
[05/16/17 22:14:19:399 EST] 0000001d SystemOut O Tue May 16 22:14:20 CEST 2017
[05/16/17 22:14:19:401 EST] 0000001d SystemOut O Waiting 90 seconds...
[05/16/17 22:14:20:398 EST] 0000076d SystemOut O Tue May 16 22:15:20 CEST 2017
[05/16/17 22:14:59:401 EST] 0000076d SystemOut O Stopping scheduler...
[05/16/17 22:14:59:401 EST] 0000076d SystemOut O Scheduler stopped.
I hope this will help you save some time. And better, more convinient code.