/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you 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 the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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. */ package org.apereo.portal.utils.threading; import java.lang.reflect.Method; import java.util.Date; import java.util.concurrent.Callable; import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.joda.time.ReadableDuration; import org.springframework.scheduling.SchedulingTaskExecutor; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.Trigger; import org.springframework.scheduling.TriggerContext; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.support.ScheduledMethodRunnable; /** * Scheduling thread pool that upon invocation of the scheduled task immediately delegates execution * to another {@link ExecutorService}. Also adds a configurable start delay to scheduled tasks * */ public class DelegatingThreadPoolTaskScheduler extends ThreadPoolTaskScheduler implements TaskScheduler, SchedulingTaskExecutor { private static final long serialVersionUID = 1L; private volatile long initialized = System.currentTimeMillis(); private volatile long lastStartDelay = 0; private ExecutorService executorService; private long initialDelay = 0; public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } /** * delay to add to the start date for all scheduled tasks * * @param initialDelay Delay before starting ANY scheduled task */ public void setInitialDelay(ReadableDuration initialDelay) { this.initialDelay = initialDelay.getMillis(); this.lastStartDelay = this.initialDelay; } @Override public void afterPropertiesSet() { this.initialized = System.currentTimeMillis(); super.afterPropertiesSet(); } /** @return the additional start delay to add to any scheduled task */ protected long getAdditionalStartDelay() { //Only bother recalculating the start delay if the last time it was resulted in a delay if (this.lastStartDelay != 0) { this.lastStartDelay = Math.max( 0, this.initialDelay - (System.currentTimeMillis() - this.initialized)); logger.debug("Calculated additionalStartDelay of: " + this.lastStartDelay); } return this.lastStartDelay; } protected Date getDelayedStartDate(Date startDate) { final long additionalStartDelay = this.getAdditionalStartDelay(); if (additionalStartDelay > 0) { final Date newStartDate = new Date(startDate.getTime() + additionalStartDelay); logger.debug( "Updated startDate with additionalStartDelay from " + startDate + " to " + newStartDate); return newStartDate; } return startDate; } protected Runnable wrapRunnable(Runnable task) { if (task instanceof ScheduledMethodRunnable) { final Method method = ((ScheduledMethodRunnable) task).getMethod(); final String methodName = method.getName(); return new ThreadNamingRunnable("-" + methodName, task); } return task; } @Override public void execute(Runnable task, long startTimeout) { task = wrapRunnable(task); final DelegatingRunnable delegatingRunnable = new DelegatingRunnable(this.executorService, task); super.execute(delegatingRunnable, startTimeout); } @Override public Future<?> submit(Runnable task) { task = wrapRunnable(task); final DelegatingRunnable delegatingRunnable = new DelegatingRunnable(this.executorService, task); return super.submit(delegatingRunnable); } @Override public <T> Future<T> submit(Callable<T> task) { final DelegatingCallable<T> delegatingCallable = new DelegatingCallable<T>(this.executorService, task); final Future<Future<T>> future = super.submit(delegatingCallable); return new DelegatingForwardingFuture<T>(future); } @Override public void execute(Runnable task) { task = wrapRunnable(task); final DelegatingRunnable delegatingRunnable = new DelegatingRunnable(this.executorService, task); super.execute(delegatingRunnable); } @Override public ScheduledFuture<Object> schedule(Runnable task, final Trigger trigger) { task = wrapRunnable(task); //Wrap the trigger so that the first call to nextExecutionTime adds in the additionalStartDelay final Trigger wrappedTrigger = new Trigger() { boolean firstExecution = false; @Override public Date nextExecutionTime(TriggerContext triggerContext) { Date nextExecutionTime = trigger.nextExecutionTime(triggerContext); if (nextExecutionTime == null) { return null; } if (firstExecution) { nextExecutionTime = getDelayedStartDate(nextExecutionTime); firstExecution = true; } return nextExecutionTime; } }; final DelegatingRunnable delegatingRunnable = new DelegatingRunnable(this.executorService, task); @SuppressWarnings("unchecked") final ScheduledFuture<ScheduledFuture<Object>> future = super.schedule(delegatingRunnable, wrappedTrigger); return new DelegatingForwardingScheduledFuture<Object>(future); } @Override public ScheduledFuture<Object> schedule(Runnable task, Date startTime) { startTime = getDelayedStartDate(startTime); final DelegatingRunnable delegatingRunnable = new DelegatingRunnable(this.executorService, task); @SuppressWarnings("unchecked") final ScheduledFuture<ScheduledFuture<Object>> future = super.schedule(delegatingRunnable, startTime); return new DelegatingForwardingScheduledFuture<Object>(future); } @Override public ScheduledFuture<Object> scheduleAtFixedRate(Runnable task, Date startTime, long period) { task = wrapRunnable(task); startTime = getDelayedStartDate(startTime); //Add scheduled task delay startTime = new Date(startTime.getTime() + period); //Add period to inital run time final DelegatingRunnable delegatingRunnable = new DelegatingRunnable(this.executorService, task); @SuppressWarnings("unchecked") final ScheduledFuture<ScheduledFuture<Object>> future = super.scheduleAtFixedRate(delegatingRunnable, startTime, period); return new DelegatingForwardingScheduledFuture<Object>(future); } @Override public ScheduledFuture<Object> scheduleAtFixedRate(Runnable task, long period) { task = wrapRunnable(task); final long additionalStartDelay = this.getAdditionalStartDelay(); if (additionalStartDelay > 0) { //If there is an additional delay use the alternate call which includes a startTime return this.scheduleAtFixedRate(task, new Date(), period); } final DelegatingRunnable delegatingRunnable = new DelegatingRunnable(this.executorService, task); @SuppressWarnings("unchecked") final ScheduledFuture<ScheduledFuture<Object>> future = super.scheduleAtFixedRate(delegatingRunnable, period); return new DelegatingForwardingScheduledFuture<Object>(future); } @Override public ScheduledFuture<Object> scheduleWithFixedDelay( Runnable task, Date startTime, long delay) { task = wrapRunnable(task); startTime = getDelayedStartDate(startTime); //Add scheduled task delay startTime = new Date(startTime.getTime() + delay); //Add period to inital run time final DelegatingRunnable delegatingRunnable = new DelegatingRunnable(this.executorService, task); @SuppressWarnings("unchecked") final ScheduledFuture<ScheduledFuture<Object>> future = super.scheduleWithFixedDelay(delegatingRunnable, startTime, delay); return new DelegatingForwardingScheduledFuture<Object>(future); } @Override public ScheduledFuture<Object> scheduleWithFixedDelay(Runnable task, long delay) { task = wrapRunnable(task); final long additionalStartDelay = this.getAdditionalStartDelay(); if (additionalStartDelay > 0) { //If there is an additional delay use the alternate call which includes a startTime return this.scheduleWithFixedDelay(task, new Date(), delay); } final DelegatingRunnable delegatingRunnable = new DelegatingRunnable(this.executorService, task); @SuppressWarnings("unchecked") final ScheduledFuture<ScheduledFuture<Object>> future = super.scheduleWithFixedDelay(delegatingRunnable, delay); return new DelegatingForwardingScheduledFuture<Object>(future); } private static class DelegatingRunnable implements Runnable { private final ExecutorService executorService; private final Runnable runnable; public DelegatingRunnable(ExecutorService executorService, Runnable runnable) { this.executorService = executorService; this.runnable = runnable; } @Override public void run() { try { executorService.submit(this.runnable); } catch (RejectedExecutionException e) { throw new RejectedExecutionException( "Failed to execute scheduled task " + this.runnable, e); } } } private static class DelegatingCallable<T> implements Callable<Future<T>> { private final ExecutorService executorService; private final Callable<T> callable; public DelegatingCallable(ExecutorService executorService, Callable<T> callable) { this.executorService = executorService; this.callable = callable; } @Override public Future<T> call() throws Exception { try { return executorService.submit(this.callable); } catch (RejectedExecutionException e) { throw new RejectedExecutionException( "Failed to execute scheduled task " + this.callable, e); } } } private static class DelegatingForwardingFuture<V> implements Future<V> { private final Future<? extends Future<V>> future; public DelegatingForwardingFuture(Future<? extends Future<V>> future) { this.future = future; } @Override public V get() throws InterruptedException, ExecutionException { return this.future.get().get(); } @Override public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return this.future.get(timeout, unit).get(timeout, unit); } @Override public boolean cancel(boolean mayInterruptIfRunning) { return this.future.cancel(mayInterruptIfRunning); } @Override public boolean isCancelled() { return this.future.isCancelled(); } @Override public boolean isDone() { return this.future.isDone(); } } private static class DelegatingForwardingScheduledFuture<V> extends DelegatingForwardingFuture<V> implements ScheduledFuture<V> { private final ScheduledFuture<ScheduledFuture<V>> future; public DelegatingForwardingScheduledFuture( ScheduledFuture<ScheduledFuture<V>> scheduledFuture) { super(scheduledFuture); this.future = scheduledFuture; } @Override public long getDelay(TimeUnit unit) { return this.future.getDelay(unit); } @Override public int compareTo(Delayed o) { return this.future.compareTo(o); } } }