So far we've written a nice job that run's without errors - but what happens if something goes wrong?
Oddjob will catch all Throwables during job execution and set the job state to Exception. Let see with an example.
package org.oddjob.devguide; public class NaughtyJob implements Runnable { public void run() { throw new RuntimeException("I won't run. I won't!"); } }
And run it...
You can try resetting the job and re-running it however this job will not start behaving! The import thing is that it's naughtiness is contained.
Oddjob will also recognise the result property and use it to either set a job complete or not complete. If the result is 0 the job is state is complete, any other value and it's not complete.
package org.oddjob.devguide; public class NotCompleteJob implements Runnable { public int getResult() { return 1; } public void run() { } }
And run it...
To stop a job Oddjob will interrupt the thread it's running. Long running jobs should be written to cope with this.
Here's an example.
package org.oddjob.devguide; public class StoppingJob implements Runnable { public void run() { synchronized(this) { try { wait(); } catch (InterruptedException e) { System.out.println("OK - I'll stop."); } } } }
Oddjob uses slf4j via
logback. If
you also use slf4j, any logging in your job's run()
method
will be captured by Oddjob and displayed in the log panel of Oddjob
Explorer.
Sometimes you don't want a job to run and complete, but to keep running in the background, probably providing some kind of service, such as Scheduling.
Oddjob will recognise the method signature of
public void start()
and public void stop()
and
treat that object as a service. When you run the job, the start
method will be called, but when it returns the state will still
be seen as executing. The service will be stopped when you
stop the job.
Here's an example.
package org.oddjob.devguide; import java.util.concurrent.CountDownLatch; public class SimpleService { private volatile boolean stop; private volatile Thread thread; public void start() throws InterruptedException { final CountDownLatch serviceStarted = new CountDownLatch(1); thread = new Thread(new Runnable() { public void run() { while (!stop) { System.out.println("I could be useful."); synchronized (SimpleService.this) { serviceStarted.countDown(); try { SimpleService.this.wait(); } catch (InterruptedException e) { break; } } } System.out.println("Service Stopping."); }}); thread.start(); serviceStarted.await(); } public void stop() throws InterruptedException { synchronized(this) { stop = true; notifyAll(); } if (thread != null) { thread.join(); } } }
A service has the advantage that when used in a sequential job, jobs after the service will run once the service has started, and can then use that service for something.
Because the job that runs immediately after the service may
consume that service, the writer of the service we must ensure
that the service is guaranteed to be available once the
start
method
completes (or an Exception is thrown). In our example we ensure
this is case by using a
CountDownLatch
.
The observant reader may be wondering what happens if the thread executing start has been interrupted? Won't the service be started but an Exception also thrown? Oddjob will ensure that the interrupted flag is cleared before it enters the start method so you don't need to check.
Ensuring that a service is stopped before the stop
method completes isn't as important. It depends on the nature of
the service. What happens if a service is stopped and started
quickly for instance? In our example it could be argued that it
wouldn't matter that a new thread was created before the previous
one had completed - however if we wished to write a unit test that
relied on the order of output (which you can find in the source
distribution) we need to guarantee the behaviour of stop which we
do by using
thread.join()
.
Here's a configuration that uses this service.
<oddjob> <job> <sequential> <jobs> <bean id="service" class="org.oddjob.devguide.SimpleService"/> <echo>Service Has Started.</echo> <stop job="${service}"/> <echo>Service Has Stopped.</echo> </jobs> </sequential> </job> </oddjob>
First the service is started, next an echo tells us the service has started, then the service is stopped, and finally another echo tells us the service has stopped. Here's the result of running this configuration with Oddjob.
I could be useful. Service Has Started. Service Stopping. Service Has Stopped.
Because of the way we wrote our service this order is guaranteed.
To summarise: You need to guarantee a service is started in
start()
but you can
use your discretion as to whether it's really stopped by
stop()
.
If your job implements java.io.Serializable
and Oddjob is
running with a persister then Oddjob will serialise the state of your
job when it completes. When Oddjob next runs, it re-creates the
job from the serialised form.
After your job is restored, Oddjob will continue to configure your job from the configuration file. Thus any serialised properties that appear in the configuration will be overwritten.
Asynchronous jobs complete on a different thread. They can be a Callable
that
returns a CompletableFuture<Integer>
or they can be an
AsyncJob
Services can also fail by being an FallibleComponent