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.app.App; import act.app.event.AppEventId; import act.event.AppEventListenerBase; import act.route.DuplicateRouteMappingException; import act.util.DestroyableBase; import org.osgl.$; import org.osgl.exception.ConfigurationException; import org.osgl.exception.NotAppliedException; import org.osgl.exception.UnexpectedException; import org.osgl.logging.LogManager; import org.osgl.logging.Logger; import org.osgl.util.C; import org.osgl.util.E; import org.osgl.util.S; import java.util.*; class _Job extends DestroyableBase implements Runnable { private static final Logger logger = LogManager.get(_Job.class); private static class LockableJobList { boolean iterating; List<_Job> jobList; _Job parent; LockableJobList(_Job parent) { this.jobList = new ArrayList<_Job>(); this.parent = parent; } synchronized void clear() { jobList.clear(); } synchronized _Job add(_Job thatJob) { if (parent.isOneTime()) { thatJob.setOneTime(); } if (parent.done() || iterating) { parent.manager.now(thatJob); return parent; } jobList.add(thatJob); return parent; } synchronized void runSubJobs() { iterating = true; try { for (_Job subJob : jobList) { subJob.run(); } } finally { iterating = false; } } } static final String BRIEF_VIEW = "id,oneTime,executed,trigger"; static final String DETAIL_VIEW = "id,oneTime,executed,trigger,worker"; private static final C.Set<Class<? extends UnexpectedException>> FATAL_EXCEPTIONS = C.set( DuplicateRouteMappingException.class, ConfigurationException.class ); private String id; private App app; private boolean oneTime; private boolean executed; private AppJobManager manager; private JobTrigger trigger; private $.Func0<?> worker; private LockableJobList parallelJobs = new LockableJobList(this); private LockableJobList followingJobs = new LockableJobList(this); private LockableJobList precedenceJobs = new LockableJobList(this); _Job(String id, AppJobManager manager) { this(id, manager, null); } _Job(String id, AppJobManager manager, $.Func0<?> worker) { this(id, manager, worker, true); } _Job(String id, AppJobManager manager, $.Func0<?> worker, boolean oneTime) { this.id = id; this.manager = $.NPE(manager); this.worker = worker; this.oneTime = oneTime; this.app = manager.app(); } @Override protected void releaseResources() { worker = null; manager = null; parallelJobs.clear(); followingJobs.clear(); precedenceJobs.clear(); super.releaseResources(); } protected String brief() { return S.concat("job[", "id", "]\none time job?", S.string(oneTime), "\trigger:", trigger.toString()); } @Override public String toString() { S.Buffer sb = S.buffer(brief()); printSubJobs(parallelJobs.jobList, "parallel jobs", sb); printSubJobs(followingJobs.jobList, "following jobs", sb); printSubJobs(precedenceJobs.jobList, "precedence jobs", sb); return sb.toString(); } private static void printSubJobs(Collection<? extends _Job> subJobs, String label, S.Buffer sb) { if (null != subJobs && !subJobs.isEmpty()) { sb.append("\n").append(label); for (_Job job : subJobs) { sb.append("\n\t").append(job.brief()); } } } _Job setOneTime() { oneTime = true; return this; } boolean isOneTime() { return oneTime; } boolean done() { return executed && oneTime; } final String id() { return id; } final void trigger(JobTrigger trigger) { E.NPE(trigger); this.trigger = trigger; } final _Job addParallelJob(_Job thatJob) { return parallelJobs.add(thatJob); } final _Job addFollowingJob(_Job thatJob) { return followingJobs.add(thatJob); } final _Job addPrecedenceJob(_Job thatJob) { return precedenceJobs.add(thatJob); } @Override public void run() { invokeParallelJobs(); runPrecedenceJobs(); try { if (Act.isDev() && app.isStarted()) { app.checkUpdates(false); } doJob(); } catch (RuntimeException e) { boolean isFatal = FATAL_EXCEPTIONS.contains(e.getClass()); Throwable cause = e; if (!isFatal) { cause = e.getCause(); while (null != cause) { isFatal = FATAL_EXCEPTIONS.contains(cause.getClass()); if (isFatal) { break; } cause = cause.getCause(); } } if (isFatal) { if (Act.isDev()) { app.setBlockIssue(e); } else { Act.shutdownApp(App.instance()); destroy(); if (App.instance().isMainThread()) { if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } throw e; } else { logger.fatal(cause, "Fatal error executing job %s", id()); } } return; } // TODO inject Job Exception Handling mechanism here logger.warn(e, "error executing job %s", id()); } finally { if (!isDestroyed()) { executed = true; if (isOneTime()) { App app = App.instance(); if (app.isStarted()) { manager.removeJob(this); } else { app.eventBus().bind(AppEventId.POST_START, new AppEventListenerBase() { @Override public void on(EventObject event) throws Exception { manager.removeJob(_Job.this); } }); } } } } runFollowingJobs(); } protected void _before() {} protected void doJob() { try { _before(); if (null != worker) { worker.apply(); } } finally { scheduleNextInvocation(); _finally(); } } protected void _finally() {} protected void cancel() { manager.cancel(id()); } private void runPrecedenceJobs() { precedenceJobs.runSubJobs(); } private void runFollowingJobs() { followingJobs.runSubJobs(); } private void invokeParallelJobs() { parallelJobs.runSubJobs(); } protected final AppJobManager manager() { return manager; } protected void scheduleNextInvocation() { if (null != trigger) trigger.scheduleFollowingCalls(manager(), this); } private static _Job of(String jobId, final Runnable runnable, AppJobManager manager, boolean oneTime) { return new _Job(jobId, manager, new $.F0() { @Override public Object apply() throws NotAppliedException, $.Break { runnable.run(); return null; } }, oneTime); } private static _Job of(final Runnable runnable, AppJobManager manager, boolean oneTime) { return of(Act.cuid(), runnable, manager, oneTime); } static _Job once(final Runnable runnable, AppJobManager manager) { return of(runnable, manager, true); } static _Job once(String jobId, final Runnable runnable, AppJobManager manager) { return of(jobId, runnable, manager, true); } static _Job multipleTimes(final Runnable runnable, AppJobManager manager) { return of(runnable, manager, false); } static _Job multipleTimes(String jobId, final Runnable runnable, AppJobManager manager) { return of(jobId, runnable, manager, false); } private static String uuid() { return UUID.randomUUID().toString(); } }