diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/JndiTaskExecutorAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/JndiTaskExecutorAutoConfiguration.java new file mode 100644 index 000000000000..6ffa3767cd09 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/JndiTaskExecutorAutoConfiguration.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * 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.springframework.boot.autoconfigure.task; + +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.concurrent.DefaultManagedTaskExecutor; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for a JNDI located + * {@link TaskExecutor}. + * + * @author Vedran Pavic + * @since 1.4.0 + */ +@Configuration +@AutoConfigureBefore(TaskExecutorAutoConfiguration.class) +@ConditionalOnProperty(prefix = "spring.task", name = "jndi-name") +@EnableConfigurationProperties(TaskExecutorProperties.class) +public class JndiTaskExecutorAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public TaskExecutor taskExecutor(TaskExecutorProperties properties) { + DefaultManagedTaskExecutor taskExecutor = new DefaultManagedTaskExecutor(); + taskExecutor.setJndiName(properties.getJndiName()); + return taskExecutor; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorAutoConfiguration.java new file mode 100644 index 000000000000..c4939e05b97c --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorAutoConfiguration.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * 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.springframework.boot.autoconfigure.task; + +import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link TaskExecutor}. + * + * @author Vedran Pavic + * @since 1.4.0 + */ +@Configuration +@EnableConfigurationProperties(TaskExecutorProperties.class) +public class TaskExecutorAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public TaskExecutor taskExecutor(TaskExecutorProperties properties) { + ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); + taskExecutor.setMaxPoolSize(properties.getPool().getMaxSize()); + taskExecutor.setThreadNamePrefix("task-executor-"); + taskExecutor.setRejectedExecutionHandler(new CallerRunsPolicy()); + return taskExecutor; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorProperties.java new file mode 100644 index 000000000000..93f12aa04763 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorProperties.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * 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.springframework.boot.autoconfigure.task; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for the task executor. + * + * @author Vedran Pavic + * @since 1.4.0 + */ +@ConfigurationProperties(prefix = "spring.task") +public class TaskExecutorProperties { + + /** + * JNDI location of the executor to delegate to. + */ + private String jndiName; + + private Pool pool = new Pool(); + + public String getJndiName() { + return this.jndiName; + } + + public void setJndiName(String jndiName) { + this.jndiName = jndiName; + } + + public Pool getPool() { + return this.pool; + } + + public static class Pool { + + /** + * Maximum pool size for the executor. + */ + private Integer maxSize = 10; + + public Integer getMaxSize() { + return this.maxSize; + } + + public void setMaxSize(Integer maxSize) { + this.maxSize = maxSize; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/package-info.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/package-info.java new file mode 100644 index 000000000000..2c234dddbfe7 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * 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. + */ + +/** + * Auto-configuration for TaskExecutor. + */ +package org.springframework.boot.autoconfigure.task; diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 5afd06d3ff98..9af2d4d7f7bd 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -84,6 +84,8 @@ org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\ org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\ org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\ org.springframework.boot.autoconfigure.velocity.VelocityAutoConfiguration,\ +org.springframework.boot.autoconfigure.task.JndiTaskExecutorAutoConfiguration,\ +org.springframework.boot.autoconfigure.task.TaskExecutorAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/JndiTaskExecutorAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/JndiTaskExecutorAutoConfigurationTests.java new file mode 100644 index 000000000000..0cb4e3064040 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/JndiTaskExecutorAutoConfigurationTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * 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.springframework.boot.autoconfigure.task; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.naming.Context; +import javax.naming.NamingException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.boot.autoconfigure.jndi.JndiPropertiesHidingClassLoader; +import org.springframework.boot.autoconfigure.jndi.TestableInitialContextFactory; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.concurrent.DefaultManagedTaskExecutor; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link JndiTaskExecutorAutoConfiguration}. + * + * @author Vedran Pavic + */ +public class JndiTaskExecutorAutoConfigurationTests { + + private final AnnotationConfigApplicationContext context = + new AnnotationConfigApplicationContext(); + + private String initialContextFactory; + + private ClassLoader threadContextClassLoader; + + @Before + public void setupJndi() { + this.initialContextFactory = System.getProperty(Context.INITIAL_CONTEXT_FACTORY); + System.setProperty(Context.INITIAL_CONTEXT_FACTORY, + TestableInitialContextFactory.class.getName()); + } + + @Before + public void setupThreadContextClassLoader() { + this.threadContextClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader( + new JndiPropertiesHidingClassLoader(getClass().getClassLoader())); + } + + @After + public void close() { + TestableInitialContextFactory.clearAll(); + if (this.initialContextFactory != null) { + System.setProperty(Context.INITIAL_CONTEXT_FACTORY, + this.initialContextFactory); + } + else { + System.clearProperty(Context.INITIAL_CONTEXT_FACTORY); + } + if (this.context != null) { + this.context.close(); + } + Thread.currentThread().setContextClassLoader(this.threadContextClassLoader); + } + + @Test + public void taskExecutorIsAvailableFromJndi() + throws IllegalStateException, NamingException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + configureJndi("foo", executorService); + + EnvironmentTestUtils.addEnvironment(this.context, "spring.task.jndi-name=foo"); + registerAndRefresh(JndiTaskExecutorAutoConfiguration.class); + + assertThat(this.context.getBean(TaskExecutor.class)) + .isInstanceOf(DefaultManagedTaskExecutor.class); + } + + private void configureJndi(String name, ExecutorService executorService) + throws IllegalStateException, NamingException { + TestableInitialContextFactory.bind(name, executorService); + } + + private void registerAndRefresh(Class... annotatedClasses) { + this.context.register(annotatedClasses); + this.context.refresh(); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutorAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutorAutoConfigurationTests.java new file mode 100644 index 000000000000..3a827b17eacc --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutorAutoConfigurationTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * 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.springframework.boot.autoconfigure.task; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link TaskExecutorAutoConfiguration}. + * + * @author Vedran Pavic + */ +public class TaskExecutorAutoConfigurationTests { + + private final AnnotationConfigApplicationContext context = + new AnnotationConfigApplicationContext(); + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void defaultTaskExecutorExists() { + registerAndRefresh(TaskExecutorAutoConfiguration.class); + + assertThat(this.context.getBean(TaskExecutor.class)) + .isInstanceOf(ThreadPoolTaskExecutor.class); + } + + @Test + public void customMaxPoolSize() { + EnvironmentTestUtils.addEnvironment(this.context, "spring.task.pool.max-size=5"); + registerAndRefresh(TaskExecutorAutoConfiguration.class); + + ThreadPoolTaskExecutor taskExecutor = + this.context.getBean(ThreadPoolTaskExecutor.class); + assertThat(taskExecutor.getMaxPoolSize()).isEqualTo(5); + } + + private void registerAndRefresh(Class... annotatedClasses) { + this.context.register(annotatedClasses); + this.context.refresh(); + } + +}