/**
* Copyright (C) 2015 Zalando SE (http://tech.zalando.com)
*
* 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.
*/
package org.zalando.spring.boot.scheduling;
import static org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME;
import static org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
@Configuration
@EnableScheduling
public class CustomSchedulingConfiguration {
@Configuration
@AutoConfigureAfter(DefaultSchedulingConfiguration.class)
static class SchedulingConfigurerConfiguration {
@Autowired
private SchedulingProperties properties;
@Bean
public CustomSchedulingConfigurer customSchedulingConfigurer() {
return new CustomSchedulingConfigurer(properties);
}
}
@Configuration
@ConditionalOnProperty(prefix = "scheduling.executor", name = "enabled", matchIfMissing = true)
public static class DefaultSchedulingConfiguration {
private final Logger log = LoggerFactory.getLogger(DefaultSchedulingConfiguration.class);
@Autowired(required = false)
@Qualifier(DEFAULT_TASK_EXECUTOR_BEAN_NAME)
private TaskExecutor taskExecutor;
@Autowired
private SchedulingProperties properties;
@Bean(destroyMethod = "shutdown")
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledExecutorService scheduledExecutorService() {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(properties.getCorePoolSize());
executor.setMaximumPoolSize(properties.getMaxPoolSize());
executor.setThreadFactory(new CustomizableThreadFactory(properties.getThreadNamePrefix()));
executor.setRejectedExecutionHandler(getConfiguredRejectedExecutionHandler());
return executor;
}
private RejectedExecutionHandler getConfiguredRejectedExecutionHandler() {
try {
return (RejectedExecutionHandler) Class.forName(properties.getRejectedExecutionHandler()).newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage(), e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
@Bean(name = DEFAULT_TASK_SCHEDULER_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TaskScheduler taskScheduler() {
if (taskExecutor != null) {
log.debug("create task-scheduler with pre-configured executor ...");
return new ConcurrentTaskScheduler(taskExecutor, scheduledExecutorService());
} else {
log.debug("create task-scheduler without pre-configured executor ...");
return new ConcurrentTaskScheduler(scheduledExecutorService());
}
}
}
static class CustomSchedulingConfigurer implements SchedulingConfigurer, BeanFactoryAware, DisposableBean {
private final Logger log = LoggerFactory.getLogger(CustomSchedulingConfigurer.class);
private final SchedulingProperties properties;
private ScheduledExecutorService localExecutor;
private BeanFactory beanFactory;
public CustomSchedulingConfigurer(SchedulingProperties properties) {
this.properties = properties;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
if (properties.isEnabled()) {
TaskScheduler taskScheduler = null;
try {
taskScheduler = this.beanFactory.getBean(TaskScheduler.class);
} catch (NoUniqueBeanDefinitionException e) {
taskScheduler = this.beanFactory.getBean("taskScheduler", TaskScheduler.class);
} catch (NoSuchBeanDefinitionException ex) {
log.warn("'useExistingScheduler' was configured to 'true', but we did not find any bean.");
}
if (taskScheduler != null) {
log.info("register existing TaskScheduler");
taskRegistrar.setScheduler(taskScheduler);
} else {
throw new BeanCreationException("Expecting a 'ConcurrentTaskScheduler' injected, but was 'null'");
}
} else {
log.info("'CustomSchedulingConfiguration' is disabled, create a default - 'ConcurrentTaskScheduler'");
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
taskRegistrar.setScheduler(new ConcurrentTaskScheduler(localExecutor));
}
}
@Override
public void destroy() throws Exception {
if (this.localExecutor != null) {
this.localExecutor.shutdownNow();
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}
}