package act.job;
/*-
* #%L
* ACT Framework
* %%
* Copyright (C) 2014 - 2017 ActFramework
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import act.app.App;
import act.app.event.AppEventId;
import act.conf.AppConfig;
import act.event.AppEventListenerBase;
import fc.cron.CronExpression;
import org.joda.time.DateTime;
import org.joda.time.Seconds;
import org.osgl.$;
import org.osgl.exception.NotAppliedException;
import org.osgl.logging.L;
import org.osgl.logging.Logger;
import org.osgl.util.E;
import org.osgl.util.S;
import org.rythmengine.utils.Time;
import java.util.EventObject;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static act.app.event.AppEventId.START;
import static act.app.event.AppEventId.STOP;
import static act.job.AppJobManager.appEventJobId;
abstract class JobTrigger {
protected static Logger logger = L.get(App.class);
@Override
public String toString() {
return getClass().getSimpleName();
}
final void register(_Job job, AppJobManager manager) {
job.trigger(this);
manager.addJob(job);
schedule(manager, job);
}
void scheduleFollowingCalls(AppJobManager manager, _Job job) {}
void schedule(AppJobManager manager, _Job job) {}
static JobTrigger of(AppConfig config, Cron anno) {
String v = anno.value();
if (v.startsWith("cron.")) {
v = (String) config.get(v);
} else if (v.startsWith("${") && v.endsWith("}")) {
v = v.substring(2, v.length() - 1);
v = (String) config.get(v);
}
if (S.blank(v)) {
throw E.invalidConfiguration("Cannot find configuration for cron: %s", anno.value());
}
return cron(v);
}
static JobTrigger of(AppConfig config, OnAppStart anno) {
if (anno.async()) {
return alongWith(START);
} else {
return after(START);
}
}
static JobTrigger of(AppConfig config, OnAppStop anno) {
if (anno.async()) {
return alongWith(STOP);
} else {
return before(STOP);
}
}
static JobTrigger of(AppConfig config, FixedDelay anno) {
String delay = anno.value();
if (delay.startsWith("delay.")) {
delay = (String) config.get(delay);
} else if (delay.startsWith("${") && delay.endsWith("}")) {
delay = delay.substring(2, delay.length() - 1);
delay = (String) config.get(delay);
}
if (S.blank(delay)) {
throw E.invalidConfiguration("Cannot find configuration for delay: %s", anno.value());
}
return fixedDelay(delay);
}
static JobTrigger of(AppConfig config, Every anno) {
String duration = anno.value();
if (duration.startsWith("every.")) {
duration = (String) config.get(duration);
} else if (duration.startsWith("${") && duration.endsWith("}")) {
duration = duration.substring(2, duration.length() - 1);
duration = (String) config.get(duration);
}
if (S.blank(duration)) {
throw E.invalidConfiguration("Cannot find configuration for duration: %s", anno.value());
}
return every(duration);
}
static JobTrigger of(AppConfig config, AlongWith anno) {
String id = anno.value();
E.illegalArgumentIf(S.blank(id), "associate job ID cannot be empty");
return new _AlongWith(id);
}
static JobTrigger of(AppConfig config, InvokeAfter anno) {
String id = anno.value();
E.illegalArgumentIf(S.blank(id), "associate job ID cannot be empty");
return new _After(id);
}
static JobTrigger of(AppConfig config, InvokeBefore anno) {
String id = anno.value();
E.illegalArgumentIf(S.blank(id), "associate job ID cannot be empty");
return new _Before(id);
}
static JobTrigger cron(String expression) {
return new _Cron(expression);
}
static JobTrigger fixedDelay(String duration) {
return new _FixedDelay(duration);
}
static JobTrigger fixedDelay(long seconds) {
return new _FixedDelay(seconds);
}
static JobTrigger fixedDelay(long interval, TimeUnit timeUnit) {
return new _FixedDelay(timeUnit.toSeconds(interval));
}
static JobTrigger every(String duration) {
return new _Every(duration);
}
static JobTrigger every(long seconds) {
return new _Every(seconds, TimeUnit.SECONDS);
}
static JobTrigger every(long duration, TimeUnit timeUnit) {
return new _Every(duration, timeUnit);
}
static JobTrigger onAppStart(boolean async) {
return async ? alongWith(START) : after(START);
}
static JobTrigger onAppStop(boolean async) {
return async ? alongWith(STOP) : before(STOP);
}
static JobTrigger onAppEvent(AppEventId eventId, boolean async) {
return async ? alongWith(eventId) : after(eventId);
}
static JobTrigger delayForSeconds(long seconds) {
return new _FixedDelay(seconds);
}
static JobTrigger alongWith(String jobId) {
return new _AlongWith(jobId);
}
static JobTrigger alongWith(AppEventId appEvent) {
return new _AlongWith(appEventJobId(appEvent));
}
static JobTrigger before(String jobId) {
return new _Before(jobId);
}
static JobTrigger before(AppEventId appEvent) {
return before(appEventJobId(appEvent));
}
static JobTrigger after(String jobId) {
return new _After(jobId);
}
static JobTrigger after(AppEventId appEvent) {
return after(appEventJobId(appEvent));
}
static class _Cron extends JobTrigger {
private CronExpression cronExpr;
_Cron(String expression) {
cronExpr = new CronExpression(expression);
}
@Override
public String toString() {
return S.newBuffer("cron :").a(cronExpr).toString();
}
@Override
void schedule(final AppJobManager manager, final _Job job) {
App app = manager.app();
if (!app.isStarted()) {
app.eventBus().bindAsync(AppEventId.POST_START, new AppEventListenerBase() {
@Override
public void on(EventObject event) throws Exception {
delayedSchedule(manager, job);
}
});
} else {
delayedSchedule(manager, job);
}
}
private void delayedSchedule(AppJobManager manager, _Job job) {
DateTime now = DateTime.now();
// add one seconds to prevent the next time be the current time (now)
DateTime next = cronExpr.nextTimeAfter(now.plusSeconds(1));
Seconds seconds = Seconds.secondsBetween(now, next);
ScheduledFuture future = manager.executor().schedule(job, seconds.getSeconds(), TimeUnit.SECONDS);
manager.futureScheduled(job.id(), future);
}
@Override
void scheduleFollowingCalls(AppJobManager manager, _Job job) {
schedule(manager, job);
}
}
private abstract static class _Periodical extends JobTrigger {
protected long seconds;
_Periodical(String duration) {
E.illegalArgumentIf(S.blank(duration), "delay duration shall not be empty");
seconds = Time.parseDuration(duration);
E.illegalArgumentIf(seconds < 1, "delay duration shall not be zero or negative number");
}
_Periodical(long seconds) {
E.illegalArgumentIf(seconds < 1, "delay duration cannot be zero or negative");
this.seconds = seconds;
}
}
private static class _FixedDelay extends _Periodical {
_FixedDelay(String duration) {
super(duration);
}
_FixedDelay(long seconds) {
super(seconds);
}
@Override
public String toString() {
return S.concat("fixed delay of ", S.string(seconds), " seconds");
}
@Override
void schedule(final AppJobManager manager, final _Job job) {
App app = manager.app();
if (!app.isStarted()) {
app.eventBus().bindAsync(AppEventId.POST_START, new AppEventListenerBase() {
@Override
public void on(EventObject event) throws Exception {
delayedSchedule(manager, job);
}
});
} else {
delayedSchedule(manager, job);
}
}
private void delayedSchedule(AppJobManager manager, _Job job) {
ScheduledThreadPoolExecutor executor = manager.executor();
ScheduledFuture future = executor.scheduleWithFixedDelay(job, seconds, seconds, TimeUnit.SECONDS);
manager.futureScheduled(job.id(), future);
}
}
private static class _Every extends _Periodical {
_Every(String duration) {
super(duration);
}
_Every(long duration, TimeUnit timeUnit) {
super(timeUnit.toSeconds(duration));
}
@Override
public String toString() {
return S.concat("every ", S.string(seconds), " seconds");
}
@Override
void schedule(final AppJobManager manager, final _Job job) {
App app = manager.app();
if (!app.isStarted()) {
app.eventBus().bindAsync(AppEventId.POST_START, new AppEventListenerBase() {
@Override
public void on(EventObject event) throws Exception {
delayedSchedule(manager, job);
}
});
} else {
delayedSchedule(manager, job);
}
}
private void delayedSchedule(AppJobManager manager, _Job job) {
ScheduledThreadPoolExecutor executor = manager.executor();
ScheduledFuture future = executor.scheduleAtFixedRate(job, seconds, seconds, TimeUnit.SECONDS);
manager.futureScheduled(job.id(), future);
}
}
private abstract static class _AssociatedTo extends JobTrigger {
protected String id;
_AssociatedTo(String id) {
E.illegalArgumentIf(S.blank(id), "associate job ID expected");
this.id = id;
}
@Override
void schedule(AppJobManager manager, _Job job) {
if (null == id) {
logger.warn("Failed to register job because target job not found: %s. Will try again after app started", id);
scheduleDelayedRegister(manager, job);
} else {
_Job associateTarget = manager.jobById(id);
if (null == associateTarget) {
logger.warn("Cannot find associated job: %s", id);
} else {
associate(job, associateTarget);
}
}
}
private void scheduleDelayedRegister(final AppJobManager manager, final _Job job) {
final String id = delayedRegisterJobId(job);
before(START).register(new _Job(id, manager, new $.F0<Void>() {
@Override
public Void apply() throws NotAppliedException, $.Break {
_Job associateTo = manager.jobById(id);
if (null == associateTo) {
logger.warn("Cannot find associated job: %s", id);
} else {
associate(job, associateTo);
}
return null;
}
}), manager);
}
private String delayedRegisterJobId(_Job job) {
return S.concat("delayed_association_register-", job.id(), "-to-", id);
}
abstract void associate(_Job theJob, _Job toJob);
}
private static class _AlongWith extends _AssociatedTo {
_AlongWith(String id) {
super(id);
}
@Override
public String toString() {
return S.concat("along with ", id);
}
@Override
void associate(_Job theJob, _Job toJob) {
toJob.addParallelJob(theJob);
}
}
private static class _Before extends _AssociatedTo {
_Before(String id) {
super(id);
}
@Override
public String toString() {
return S.concat("before ", id);
}
@Override
void associate(_Job theJob, _Job toJob) {
toJob.addPrecedenceJob(theJob);
}
}
private static class _After extends _AssociatedTo {
_After(String id) {
super(id);
}
@Override
public String toString() {
return S.concat("after ", id);
}
@Override
void associate(_Job theJob, _Job toJob) {
toJob.addFollowingJob(theJob);
}
}
}