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.Act;
import act.Destroyable;
import act.app.App;
import act.app.AppServiceBase;
import act.app.AppThreadFactory;
import act.app.event.AppEventId;
import act.event.AppEventListenerBase;
import act.event.OnceEventListenerBase;
import act.mail.MailerContext;
import org.joda.time.DateTime;
import org.joda.time.Seconds;
import org.osgl.$;
import org.osgl.exception.NotAppliedException;
import org.osgl.util.C;
import org.osgl.util.E;
import org.osgl.util.S;
import org.rythmengine.utils.Time;
import java.util.EventObject;
import java.util.Map;
import java.util.concurrent.*;
public class AppJobManager extends AppServiceBase<AppJobManager> {
private ScheduledThreadPoolExecutor executor;
private ConcurrentMap<String, _Job> jobs = new ConcurrentHashMap<String, _Job>();
private ConcurrentMap<String, ScheduledFuture> scheduled = new ConcurrentHashMap<>();
static String appEventJobId(AppEventId eventId) {
return S.concat("__act_app__", eventId.toString().toLowerCase());
}
public AppJobManager(App app) {
super(app);
initExecutor(app);
for (AppEventId appEventId : AppEventId.values()) {
createAppEventListener(appEventId);
}
}
@Override
protected void releaseResources() {
for (_Job job : jobs.values()) {
job.destroy();
}
jobs.clear();
executor.shutdown();
executor.getQueue().clear();
}
public <T> Future<T> now(Callable<T> callable) {
return executor().submit(callable);
}
public void now(Runnable runnable) {
executor().submit(wrap(runnable));
}
public <T> Future<T> delay(Callable<T> callable, long delay, TimeUnit timeUnit) {
return executor().schedule(callable, delay, timeUnit);
}
public void delay(Runnable runnable, long delay, TimeUnit timeUnit) {
executor().schedule(wrap(runnable), delay, timeUnit);
}
public <T> Future<T> delay(Callable<T> callable, String delay) {
int seconds = parseTime(delay);
return executor().schedule(callable, seconds, TimeUnit.SECONDS);
}
public void delay(Runnable runnable, String delay) {
int seconds = parseTime(delay);
executor().schedule(wrap(runnable), seconds, TimeUnit.SECONDS);
}
public void every(String id, Runnable runnable, String interval) {
JobTrigger.every(interval).schedule(this, _Job.multipleTimes(id, runnable, this));
}
public void every(Runnable runnable, String interval) {
JobTrigger.every(interval).schedule(this, _Job.multipleTimes(runnable, this));
}
public void every(Runnable runnable, long interval, TimeUnit timeUnit) {
JobTrigger.every(interval, timeUnit).schedule(this, _Job.multipleTimes(runnable, this));
}
public void every(String id, Runnable runnable, long interval, TimeUnit timeUnit) {
JobTrigger.every(interval, timeUnit).schedule(this, _Job.multipleTimes(id, runnable, this));
}
public void fixedDelay(Runnable runnable, String interval) {
JobTrigger.every(interval).schedule(this, _Job.multipleTimes(runnable, this));
}
public void fixedDelay(String id, Runnable runnable, String interval) {
JobTrigger.every(interval).schedule(this, _Job.multipleTimes(id, runnable, this));
}
public void fixedDelay(Runnable runnable, long interval, TimeUnit timeUnit) {
JobTrigger.fixedDelay(interval, timeUnit).schedule(this, _Job.multipleTimes(runnable, this));
}
public void fixedDelay(String id, Runnable runnable, long interval, TimeUnit timeUnit) {
JobTrigger.fixedDelay(interval, timeUnit).schedule(this, _Job.multipleTimes(id, runnable, this));
}
private int parseTime(String timeDuration) {
if (timeDuration.startsWith("${") && timeDuration.endsWith("}")) {
timeDuration = (String) app().config().get(timeDuration.substring(2, timeDuration.length() - 1));
}
return Time.parseDuration(timeDuration);
}
public void on(DateTime instant, Runnable runnable) {
DateTime now = DateTime.now();
E.illegalArgumentIf(instant.isBefore(now));
Seconds seconds = Seconds.secondsBetween(now, instant);
executor().schedule(wrap(runnable), seconds.getSeconds(), TimeUnit.SECONDS);
}
public <T> Future<T> on(DateTime instant, Callable<T> callable) {
DateTime now = DateTime.now();
E.illegalArgumentIf(instant.isBefore(now));
Seconds seconds = Seconds.secondsBetween(now, instant);
return executor().schedule(callable, seconds.getSeconds(), TimeUnit.SECONDS);
}
public void on(AppEventId appEvent, final Runnable runnable) {
on(appEvent, runnable, false);
}
public void on(AppEventId appEvent, final Runnable runnable, boolean runImmediatelyIfEventDispatched) {
_Job job = jobById(appEventJobId(appEvent));
if (null == job) {
processDelayedJob(wrap(runnable), runImmediatelyIfEventDispatched);
} else {
job.addPrecedenceJob(_Job.once(runnable, this));
}
}
public void post(AppEventId appEvent, final Runnable runnable) {
post(appEvent, runnable, false);
}
public void post(AppEventId appEvent, final Runnable runnable, boolean runImmediatelyIfEventDispatched) {
_Job job = jobById(appEventJobId(appEvent));
if (null == job) {
processDelayedJob(wrap(runnable), runImmediatelyIfEventDispatched);
} else {
job.addFollowingJob(_Job.once(runnable, this));
}
}
public void on(AppEventId appEvent, String jobId, final Runnable runnable) {
on(appEvent, jobId, runnable, false);
}
public void on(AppEventId appEvent, String jobId, final Runnable runnable, boolean runImmediatelyIfEventDispatched) {
_Job job = jobById(appEventJobId(appEvent));
if (null == job) {
processDelayedJob(wrap(runnable), runImmediatelyIfEventDispatched);
} else {
job.addPrecedenceJob(_Job.once(jobId, runnable, this));
}
}
public void post(AppEventId appEvent, String jobId, final Runnable runnable) {
post(appEvent, jobId, runnable, false);
}
public void post(AppEventId appEvent, String jobId, final Runnable runnable, boolean runImmediatelyIfEventDispatched) {
_Job job = jobById(appEventJobId(appEvent));
if (null == job) {
processDelayedJob(wrap(runnable), runImmediatelyIfEventDispatched);
} else {
job.addFollowingJob(_Job.once(jobId, runnable, this));
}
}
private void processDelayedJob(final Runnable runnable, boolean runImmediatelyIfEventDispatched) {
if (runImmediatelyIfEventDispatched) {
try {
runnable.run();
} catch (Exception e) {
Act.LOGGER.error(e, "Error running job");
}
} else {
now(runnable);
}
}
/**
* Cancel a scheduled Job by ID
* @param jobId the job Id
*/
public void cancel(String jobId) {
_Job job = jobById(jobId);
if (null != job) {
removeJob(job);
} else {
ScheduledFuture future = scheduled.remove(jobId);
if (null != future) {
future.cancel(true);
}
}
}
public void beforeAppStart(final Runnable runnable) {
on(AppEventId.START, runnable);
}
public void afterAppStart(final Runnable runnable) {
post(AppEventId.START, runnable);
}
public void beforeAppStop(final Runnable runnable) {
on(AppEventId.STOP, runnable);
}
C.List<_Job> jobs() {
return C.list(jobs.values());
}
C.List<_Job> virtualJobs() {
final AppJobManager jobManager = Act.jobManager();
return C.list(scheduled.entrySet()).map(new $.Transformer<Map.Entry<String, ScheduledFuture>, _Job>() {
@Override
public _Job transform(Map.Entry<String, ScheduledFuture> entry) {
return new _Job(entry.getKey(), jobManager);
}
});
}
void futureScheduled(String id, ScheduledFuture future) {
scheduled.putIfAbsent(id, future);
}
_Job jobById(String id) {
_Job job = jobs.get(id);
if (null == job) {
ScheduledFuture future = scheduled.get(id);
if (null != future) {
return new _Job(id, Act.jobManager());
}
Act.LOGGER.warn("cannot find job by id: %s", id);
}
return job;
}
void addJob(_Job job) {
jobs.put(job.id(), job);
}
void removeJob(_Job job) {
String id = job.id();
jobs.remove(id);
ScheduledFuture future = scheduled.remove(id);
if (null != future) {
future.cancel(true);
}
}
ScheduledThreadPoolExecutor executor() {
return executor;
}
private void initExecutor(App app) {
int poolSize = app.config().jobPoolSize();
executor = new ScheduledThreadPoolExecutor(poolSize, new AppThreadFactory("jobs"), new ThreadPoolExecutor.AbortPolicy());
//JDK1.7 API: executor.setRemoveOnCancelPolicy(true);
}
private void createAppEventListener(AppEventId appEventId) {
String jobId = appEventJobId(appEventId);
_Job job = new _Job(jobId, this);
addJob(job);
app().eventBus().bind(appEventId, new _AppEventListener(jobId, job));
}
private static class _AppEventListener extends AppEventListenerBase {
private Runnable worker;
_AppEventListener(String id, Runnable worker) {
super(id);
this.worker = $.NPE(worker);
}
@Override
public void on(EventObject event) throws Exception {
worker.run();
}
@Override
protected void releaseResources() {
if (null != worker && worker instanceof Destroyable) {
((Destroyable) worker).destroy();
}
}
}
private Runnable wrap(Runnable runnable) {
return new ContextualJob(app().cuid(), runnable);
}
private Runnable wrap(final Callable callable) {
return new Runnable() {
@Override
public void run() {
try {
callable.call();
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw E.unexpected(e);
}
}
};
}
private class ContextualJob extends _Job {
private JobContext origin_ = JobContext.copy();
ContextualJob(String id, final Runnable runnable) {
super(id, AppJobManager.this, new $.F0() {
@Override
public Object apply() throws NotAppliedException, $.Break {
runnable.run();
return null;
}
}, true);
app().eventBus().once(MailerContext.InitEvent.class, new OnceEventListenerBase<MailerContext.InitEvent>() {
@Override
public boolean tryHandle(MailerContext.InitEvent event) throws Exception {
MailerContext mailerContext = MailerContext.current();
if (null != mailerContext) {
_before();
return true;
}
return true;
}
});
}
@Override
protected void _before() {
// copy the JobContext of parent thread into the current thread
JobContext.init(origin_);
}
@Override
protected void _finally() {
JobContext.clear();
}
}
}