package oncue.timedjobs;
import static akka.pattern.Patterns.ask;
import static java.lang.String.format;
import java.util.Map;
import oncue.common.messages.EnqueueJob;
import oncue.common.messages.RetryTimedJobMessage;
import oncue.common.settings.Settings;
import oncue.common.settings.SettingsProvider;
import scala.concurrent.Await;
import akka.actor.ActorRef;
import akka.camel.CamelMessage;
import akka.camel.javaapi.UntypedConsumerActor;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.util.Timeout;
/**
* A TimedJob is created from an entry in the timetable in the configuration file. It will enqueue
* the specified job according to the specified Quartz schedule.
*
* If the job cannot be queued due to communications errors, it will wait for the API timeout period
* defined in the settings before trying again. Currently it will try forever.
*
* See http://camel.apache.org/quartz.html for URI specifications
*/
public class TimedJob extends UntypedConsumerActor {
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
private final Settings settings = SettingsProvider.SettingsProvider.get(getContext().system());
private final Map<String, String> params;
private final String schedule;
// An optional probe for testing
protected final ActorRef testProbe;
private final String workerType;
private Integer failureRetryCount;
public TimedJob(String workerType, String schedule, Map<String, String> params) {
this(workerType, schedule, params, null, null);
}
public TimedJob(String workerType, String schedule, Map<String, String> params,
Integer failureRetryCount, ActorRef testProbe) {
this.workerType = workerType;
this.schedule = schedule;
this.testProbe = testProbe;
this.failureRetryCount = failureRetryCount;
this.params = params;
}
/**
* Enqueue the job at the Scheduler
*
* @param workerType The qualified class name of the worker to instantiate
* @param jobParameters The user-defined parameters map to pass to the job
* @throws Exception If the job cannot be enqueued
*/
private void enqueueJob(String workerType, Map<String, String> jobParameters) throws Exception {
Await.result(
ask(getContext().actorFor(settings.SCHEDULER_PATH), new EnqueueJob(workerType,
jobParameters), new Timeout(settings.SCHEDULER_TIMEOUT)),
settings.SCHEDULER_TIMEOUT);
}
@Override
public String getEndpointUri() {
return schedule;
}
@Override
public void onReceive(final Object message) throws TimedJobException {
if (testProbe != null) {
testProbe.tell(message, getSelf());
}
if (message instanceof CamelMessage) {
log.debug("Received Camel message for timed job submission for worker type {}",
workerType);
tryEnqueueJob(workerType, params);
} else if (message instanceof RetryTimedJobMessage) {
log.info("Retrying timed job submission for worker type {}", workerType);
RetryTimedJobMessage retryMessage = (RetryTimedJobMessage) message;
tryEnqueueJob(retryMessage.getWorkerType(), retryMessage.getJobParameters());
} else {
unhandled(message);
}
}
/*
* This actor will post a "note to self" on a delay, in order to re-attempt to enqueue a job at
* a later stage.
*/
private void sendRetryMessage(String workerType, Map<String, String> jobParameters) {
RetryTimedJobMessage retryMessage = new RetryTimedJobMessage(workerType, jobParameters);
getContext()
.system()
.scheduler()
.scheduleOnce(settings.TIMED_JOBS_RETRY_DELAY, getSelf(), retryMessage,
getContext().dispatcher());
}
/**
* Attempt to enqueue a job with the queue manager. If any exceptions are caught they will be
* logged, then the job will be rescheduled to run in the future.
*
* @param workerType The qualified class name of the worker to instantiate
* @param jobParameters The user-defined parameters map to pass to the job
*/
private void tryEnqueueJob(String workerType, Map<String, String> jobParameters)
throws TimedJobException {
try {
enqueueJob(workerType, jobParameters);
} catch (Exception e) {
log.error(e, "Failed to enqueue timed job for worker type {}", workerType);
if (failureRetryCount == null) {
sendRetryMessage(workerType, jobParameters);
} else {
if (failureRetryCount > 0) {
failureRetryCount -= 1;
sendRetryMessage(workerType, jobParameters);
} else {
throw new TimedJobException(
format("Failed to enqueue job for worker type '%s' after specified number of retries.",
workerType));
}
}
}
}
}