package org.threadly.concurrent.wrapper.interceptor; import java.util.concurrent.Callable; import java.util.function.BiFunction; import org.threadly.concurrent.SubmitterScheduler; import org.threadly.concurrent.future.ListenableFuture; import org.threadly.concurrent.future.ListenableFutureTask; import org.threadly.util.ArgumentVerifier; /** * Class to wrap {@link SubmitterScheduler} pool so that tasks can be intercepted and either * wrapped, or modified, before being submitted to the pool. This class can be passed a lambda to * {@link #SubmitterSchedulerTaskInterceptor(SubmitterScheduler, BiFunction)}, or * {@link #wrapTask(Runnable, boolean)} can be overridden to provide the task which should be * submitted to the {@link SubmitterScheduler}. Please see the javadocs of * {@link #wrapTask(Runnable, boolean)} for more details about ways a task can be modified or * wrapped. * <p> * Other variants of task wrappers: {@link ExecutorTaskInterceptor}, * {@link SchedulerServiceTaskInterceptor}, {@link PrioritySchedulerTaskInterceptor}. * * @since 4.6.0 */ public class SubmitterSchedulerTaskInterceptor extends ExecutorTaskInterceptor implements SubmitterScheduler { protected final SubmitterScheduler parentScheduler; protected final BiFunction<Runnable, Boolean, Runnable> taskManipulator; /** * When using this constructor, {@link #wrapTask(Runnable, boolean)} must be overridden to * handle the task manipulation before the task is submitted to the provided * {@link SubmitterScheduler}. Please see the javadocs of {@link #wrapTask(Runnable, boolean)} * for more details about ways a task can be modified or wrapped. * * @param parentExecutor An instance of {@link Executor} to wrap */ protected SubmitterSchedulerTaskInterceptor(SubmitterScheduler parentScheduler) { this(parentScheduler, (r, s) -> { throw new RuntimeException("Must override wrapTask(Runnable, Boolean)"); }); } /** * Constructs a wrapper for {@link SubmitterScheduler} pool so that tasks can be intercepted * and modified, before being submitted to the pool. * * @param parentScheduler An instance of {@link SubmitterScheduler} to wrap * @param taskManipulator A lambda to manipulate a {@link Runnable} that was submitted for execution */ public SubmitterSchedulerTaskInterceptor(SubmitterScheduler parentScheduler, BiFunction<Runnable, Boolean, Runnable> taskManipulator) { super(parentScheduler); ArgumentVerifier.assertNotNull(taskManipulator, "taskManipulator"); this.parentScheduler = parentScheduler; this.taskManipulator = taskManipulator; } /** * Overridden version which delegates to {@link #wrapTask(Runnable, boolean)}. There should be * no reason to override this, instead just ensure that {@link #wrapTask(Runnable, boolean)} is * implemented. */ @Override public final Runnable wrapTask(Runnable task) { return wrapTask(task, false); } /** * Implementation to modify a provided task. The provided runnable will be the one submitted to * the Executor, unless a {@link Callable} was submitted, in which case a * {@link ListenableFutureTask} will be provided. In the last condition the original callable * can be retrieved by invoking {@link ListenableFutureTask#getContainedCallable()}. The returned * task can not be null, but could be either the original task, a modified task, a wrapper to the * provided task, or if no action is desired * {@link org.threadly.concurrent.DoNothingRunnable#instance()} may be provided. However caution * should be used in that if a {@link ListenableFutureTask} is provided, and then never returned * (and not canceled), then the future will never complete (and thus possibly forever blocked). * So if you are doing conditional checks for {@link ListenableFutureTask} and may not * execute/return the provided task, then you should be careful to ensure * {@link ListenableFutureTask#cancel(boolean)} is invoked. * <p> * Public visibility for javadoc visibility. * * @param task A runnable that was submitted for execution * @param recurring {@code true} if the provided task is a recurring task * @return A non-null task that will be provided to the parent executor */ public Runnable wrapTask(Runnable task, boolean recurring) { return this.taskManipulator.apply(task, recurring); } @Override public void schedule(Runnable task, long delayInMs) { parentScheduler.schedule(task == null ? null : wrapTask(task, false), delayInMs); } @Override public ListenableFuture<?> submitScheduled(Runnable task, long delayInMs) { return submitScheduled(task, null, delayInMs); } @Override public <T> ListenableFuture<T> submitScheduled(Runnable task, T result, long delayInMs) { return parentScheduler.submitScheduled(task == null ? null : wrapTask(task, false), result, delayInMs); } @Override public <T> ListenableFuture<T> submitScheduled(Callable<T> task, long delayInMs) { ArgumentVerifier.assertNotNull(task, "task"); ListenableFutureTask<T> lft = new ListenableFutureTask<>(false, task); parentScheduler.schedule(wrapTask(lft, false), delayInMs); return lft; } @Override public void scheduleWithFixedDelay(Runnable task, long initialDelay, long recurringDelay) { parentScheduler.scheduleWithFixedDelay(task == null ? null : wrapTask(task, true), initialDelay, recurringDelay); } @Override public void scheduleAtFixedRate(Runnable task, long initialDelay, long period) { parentScheduler.scheduleAtFixedRate(task == null ? null : wrapTask(task, true), initialDelay, period); } }