001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.scheduling.concurrent;
018
019import java.util.Date;
020import java.util.concurrent.Executor;
021import java.util.concurrent.Executors;
022import java.util.concurrent.RejectedExecutionException;
023import java.util.concurrent.ScheduledExecutorService;
024import java.util.concurrent.ScheduledFuture;
025import java.util.concurrent.TimeUnit;
026
027import javax.enterprise.concurrent.LastExecution;
028import javax.enterprise.concurrent.ManagedScheduledExecutorService;
029
030import org.springframework.core.task.TaskRejectedException;
031import org.springframework.lang.Nullable;
032import org.springframework.scheduling.TaskScheduler;
033import org.springframework.scheduling.Trigger;
034import org.springframework.scheduling.support.SimpleTriggerContext;
035import org.springframework.scheduling.support.TaskUtils;
036import org.springframework.util.Assert;
037import org.springframework.util.ClassUtils;
038import org.springframework.util.ErrorHandler;
039
040/**
041 * Adapter that takes a {@code java.util.concurrent.ScheduledExecutorService} and
042 * exposes a Spring {@link org.springframework.scheduling.TaskScheduler} for it.
043 * Extends {@link ConcurrentTaskExecutor} in order to implement the
044 * {@link org.springframework.scheduling.SchedulingTaskExecutor} interface as well.
045 *
046 * <p>Autodetects a JSR-236 {@link javax.enterprise.concurrent.ManagedScheduledExecutorService}
047 * in order to use it for trigger-based scheduling if possible, instead of Spring's
048 * local trigger management which ends up delegating to regular delay-based scheduling
049 * against the {@code java.util.concurrent.ScheduledExecutorService} API. For JSR-236 style
050 * lookup in a Java EE 7 environment, consider using {@link DefaultManagedTaskScheduler}.
051 *
052 * <p>Note that there is a pre-built {@link ThreadPoolTaskScheduler} that allows for
053 * defining a {@link java.util.concurrent.ScheduledThreadPoolExecutor} in bean style,
054 * exposing it as a Spring {@link org.springframework.scheduling.TaskScheduler} directly.
055 * This is a convenient alternative to a raw ScheduledThreadPoolExecutor definition with
056 * a separate definition of the present adapter class.
057 *
058 * @author Juergen Hoeller
059 * @author Mark Fisher
060 * @since 3.0
061 * @see java.util.concurrent.ScheduledExecutorService
062 * @see java.util.concurrent.ScheduledThreadPoolExecutor
063 * @see java.util.concurrent.Executors
064 * @see DefaultManagedTaskScheduler
065 * @see ThreadPoolTaskScheduler
066 */
067public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements TaskScheduler {
068
069        @Nullable
070        private static Class<?> managedScheduledExecutorServiceClass;
071
072        static {
073                try {
074                        managedScheduledExecutorServiceClass = ClassUtils.forName(
075                                        "javax.enterprise.concurrent.ManagedScheduledExecutorService",
076                                        ConcurrentTaskScheduler.class.getClassLoader());
077                }
078                catch (ClassNotFoundException ex) {
079                        // JSR-236 API not available...
080                        managedScheduledExecutorServiceClass = null;
081                }
082        }
083
084
085        private ScheduledExecutorService scheduledExecutor;
086
087        private boolean enterpriseConcurrentScheduler = false;
088
089        @Nullable
090        private ErrorHandler errorHandler;
091
092
093        /**
094         * Create a new ConcurrentTaskScheduler,
095         * using a single thread executor as default.
096         * @see java.util.concurrent.Executors#newSingleThreadScheduledExecutor()
097         */
098        public ConcurrentTaskScheduler() {
099                super();
100                this.scheduledExecutor = initScheduledExecutor(null);
101        }
102
103        /**
104         * Create a new ConcurrentTaskScheduler, using the given
105         * {@link java.util.concurrent.ScheduledExecutorService} as shared delegate.
106         * <p>Autodetects a JSR-236 {@link javax.enterprise.concurrent.ManagedScheduledExecutorService}
107         * in order to use it for trigger-based scheduling if possible,
108         * instead of Spring's local trigger management.
109         * @param scheduledExecutor the {@link java.util.concurrent.ScheduledExecutorService}
110         * to delegate to for {@link org.springframework.scheduling.SchedulingTaskExecutor}
111         * as well as {@link TaskScheduler} invocations
112         */
113        public ConcurrentTaskScheduler(ScheduledExecutorService scheduledExecutor) {
114                super(scheduledExecutor);
115                this.scheduledExecutor = initScheduledExecutor(scheduledExecutor);
116        }
117
118        /**
119         * Create a new ConcurrentTaskScheduler, using the given {@link java.util.concurrent.Executor}
120         * and {@link java.util.concurrent.ScheduledExecutorService} as delegates.
121         * <p>Autodetects a JSR-236 {@link javax.enterprise.concurrent.ManagedScheduledExecutorService}
122         * in order to use it for trigger-based scheduling if possible,
123         * instead of Spring's local trigger management.
124         * @param concurrentExecutor the {@link java.util.concurrent.Executor} to delegate to
125         * for {@link org.springframework.scheduling.SchedulingTaskExecutor} invocations
126         * @param scheduledExecutor the {@link java.util.concurrent.ScheduledExecutorService}
127         * to delegate to for {@link TaskScheduler} invocations
128         */
129        public ConcurrentTaskScheduler(Executor concurrentExecutor, ScheduledExecutorService scheduledExecutor) {
130                super(concurrentExecutor);
131                this.scheduledExecutor = initScheduledExecutor(scheduledExecutor);
132        }
133
134
135        private ScheduledExecutorService initScheduledExecutor(@Nullable ScheduledExecutorService scheduledExecutor) {
136                if (scheduledExecutor != null) {
137                        this.scheduledExecutor = scheduledExecutor;
138                        this.enterpriseConcurrentScheduler = (managedScheduledExecutorServiceClass != null &&
139                                        managedScheduledExecutorServiceClass.isInstance(scheduledExecutor));
140                }
141                else {
142                        this.scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
143                        this.enterpriseConcurrentScheduler = false;
144                }
145                return this.scheduledExecutor;
146        }
147
148        /**
149         * Specify the {@link java.util.concurrent.ScheduledExecutorService} to delegate to.
150         * <p>Autodetects a JSR-236 {@link javax.enterprise.concurrent.ManagedScheduledExecutorService}
151         * in order to use it for trigger-based scheduling if possible,
152         * instead of Spring's local trigger management.
153         * <p>Note: This will only apply to {@link TaskScheduler} invocations.
154         * If you want the given executor to apply to
155         * {@link org.springframework.scheduling.SchedulingTaskExecutor} invocations
156         * as well, pass the same executor reference to {@link #setConcurrentExecutor}.
157         * @see #setConcurrentExecutor
158         */
159        public void setScheduledExecutor(@Nullable ScheduledExecutorService scheduledExecutor) {
160                initScheduledExecutor(scheduledExecutor);
161        }
162
163        /**
164         * Provide an {@link ErrorHandler} strategy.
165         */
166        public void setErrorHandler(ErrorHandler errorHandler) {
167                Assert.notNull(errorHandler, "ErrorHandler must not be null");
168                this.errorHandler = errorHandler;
169        }
170
171
172        @Override
173        @Nullable
174        public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
175                try {
176                        if (this.enterpriseConcurrentScheduler) {
177                                return new EnterpriseConcurrentTriggerScheduler().schedule(decorateTask(task, true), trigger);
178                        }
179                        else {
180                                ErrorHandler errorHandler =
181                                                (this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));
182                                return new ReschedulingRunnable(task, trigger, this.scheduledExecutor, errorHandler).schedule();
183                        }
184                }
185                catch (RejectedExecutionException ex) {
186                        throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
187                }
188        }
189
190        @Override
191        public ScheduledFuture<?> schedule(Runnable task, Date startTime) {
192                long initialDelay = startTime.getTime() - System.currentTimeMillis();
193                try {
194                        return this.scheduledExecutor.schedule(decorateTask(task, false), initialDelay, TimeUnit.MILLISECONDS);
195                }
196                catch (RejectedExecutionException ex) {
197                        throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
198                }
199        }
200
201        @Override
202        public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
203                long initialDelay = startTime.getTime() - System.currentTimeMillis();
204                try {
205                        return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), initialDelay, period, TimeUnit.MILLISECONDS);
206                }
207                catch (RejectedExecutionException ex) {
208                        throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
209                }
210        }
211
212        @Override
213        public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period) {
214                try {
215                        return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), 0, period, TimeUnit.MILLISECONDS);
216                }
217                catch (RejectedExecutionException ex) {
218                        throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
219                }
220        }
221
222        @Override
223        public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {
224                long initialDelay = startTime.getTime() - System.currentTimeMillis();
225                try {
226                        return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), initialDelay, delay, TimeUnit.MILLISECONDS);
227                }
228                catch (RejectedExecutionException ex) {
229                        throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
230                }
231        }
232
233        @Override
234        public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay) {
235                try {
236                        return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), 0, delay, TimeUnit.MILLISECONDS);
237                }
238                catch (RejectedExecutionException ex) {
239                        throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
240                }
241        }
242
243        private Runnable decorateTask(Runnable task, boolean isRepeatingTask) {
244                Runnable result = TaskUtils.decorateTaskWithErrorHandler(task, this.errorHandler, isRepeatingTask);
245                if (this.enterpriseConcurrentScheduler) {
246                        result = ManagedTaskBuilder.buildManagedTask(result, task.toString());
247                }
248                return result;
249        }
250
251
252        /**
253         * Delegate that adapts a Spring Trigger to a JSR-236 Trigger.
254         * Separated into an inner class in order to avoid a hard dependency on the JSR-236 API.
255         */
256        private class EnterpriseConcurrentTriggerScheduler {
257
258                public ScheduledFuture<?> schedule(Runnable task, final Trigger trigger) {
259                        ManagedScheduledExecutorService executor = (ManagedScheduledExecutorService) scheduledExecutor;
260                        return executor.schedule(task, new javax.enterprise.concurrent.Trigger() {
261                                @Override
262                                @Nullable
263                                public Date getNextRunTime(@Nullable LastExecution le, Date taskScheduledTime) {
264                                        return (trigger.nextExecutionTime(le != null ?
265                                                        new SimpleTriggerContext(le.getScheduledStart(), le.getRunStart(), le.getRunEnd()) :
266                                                        new SimpleTriggerContext()));
267                                }
268                                @Override
269                                public boolean skipRun(LastExecution lastExecution, Date scheduledRunTime) {
270                                        return false;
271                                }
272                        });
273                }
274        }
275
276}