From f35f8ef52da9e1e80efdb41eb324158577319752 Mon Sep 17 00:00:00 2001 From: Michael Minella Date: Fri, 11 Mar 2016 14:17:02 -0600 Subject: [PATCH] Update TaskLifecycleListener to use SmartLifecycle This commit changes the starting point of a task from the point when the ApplicationContext issues the ContextRefreshedEvent to SmartLifecycle#start. This is a more accurate point of start for a task in that all beans should now be available. It also allows us to clean up many ApplicationContext hacks that were present to get around the fact that many beans were not ready when a Task was attempting to begin. Resolves spring-cloud/spring-cloud-task#107 --- .../TaskBatchAutoConfiguration.java | 30 ++++- ...TaskBatchExecutionListenerFactoryBean.java | 46 ++----- ...a => TaskBatchExecutionListenerTests.java} | 118 +++++++++++++++++- .../configuration/DefaultTaskConfigurer.java | 26 ++-- .../SimpleTaskConfiguration.java | 97 ++++++++------ .../task/listener/TaskLifecycleListener.java | 44 +++++-- ...a => TaskListenerExecutorFactoryBean.java} | 6 +- .../support/SimpleTaskRepository.java | 4 +- .../support/TaskExecutionDaoFactoryBean.java | 47 ++----- .../task/SimpleTaskConfigurationTests.java | 76 ++++++++--- .../task/configuration/TestConfiguration.java | 11 +- .../listener/TaskExecutionListenerTests.java | 19 ++- .../support/SimpleTaskRepositoryMapTests.java | 18 +-- .../support/TaskDatabaseInitializerTests.java | 14 +-- .../TaskExecutionDaoFactoryBeanTests.java | 114 ++--------------- .../task/util/TestDefaultConfiguration.java | 13 +- .../src/main/asciidoc/features.adoc | 11 +- 17 files changed, 384 insertions(+), 310 deletions(-) rename spring-cloud-task-batch/src/test/java/org/springframework/cloud/task/batch/listener/{BatchTaskExecutionListenerTests.java => TaskBatchExecutionListenerTests.java} (62%) rename spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/annotation/{TaskListenerExecutorFactory.java => TaskListenerExecutorFactoryBean.java} (95%) diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchAutoConfiguration.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchAutoConfiguration.java index 3be0bcff..607e766c 100644 --- a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchAutoConfiguration.java +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchAutoConfiguration.java @@ -15,14 +15,19 @@ */ package org.springframework.cloud.task.batch.configuration; +import java.util.Collection; +import javax.sql.DataSource; + import org.springframework.batch.core.Job; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.task.batch.listener.TaskBatchExecutionListener; import org.springframework.cloud.task.configuration.EnableTask; -import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.cloud.task.repository.TaskExplorer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.CollectionUtils; /** * Provides auto configuration for the {@link TaskBatchExecutionListener}. @@ -39,9 +44,24 @@ public class TaskBatchAutoConfiguration { return new TaskBatchExecutionListenerBeanPostProcessor(); } - @Bean - @ConditionalOnMissingBean - public TaskBatchExecutionListenerFactoryBean taskBatchExecutionListener(ConfigurableApplicationContext context) { - return new TaskBatchExecutionListenerFactoryBean(context); + @Configuration + @ConditionalOnMissingBean(name = "taskBatchExecutionListener") + public static class TaskBatchExecutionListenerAutoconfiguration { + + @Autowired(required = false) + private Collection dataSources; + + @Bean + public TaskBatchExecutionListenerFactoryBean taskBatchExecutionListener(TaskExplorer taskExplorer) { + if(!CollectionUtils.isEmpty(dataSources) && dataSources.size() == 1) { + return new TaskBatchExecutionListenerFactoryBean(dataSources.iterator().next(), taskExplorer); + } + else if(CollectionUtils.isEmpty(dataSources)) { + return new TaskBatchExecutionListenerFactoryBean(null, taskExplorer); + } + else { + throw new IllegalStateException("Expected one datasource and found " + dataSources.size()); + } + } } } diff --git a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerFactoryBean.java b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerFactoryBean.java index 8ab333d4..9c47f5f7 100644 --- a/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerFactoryBean.java +++ b/spring-cloud-task-batch/src/main/java/org/springframework/cloud/task/batch/configuration/TaskBatchExecutionListenerFactoryBean.java @@ -27,10 +27,7 @@ import org.springframework.cloud.task.batch.listener.support.MapTaskBatchDao; import org.springframework.cloud.task.repository.TaskExplorer; import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao; import org.springframework.cloud.task.repository.support.SimpleTaskExplorer; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; /** * {@link FactoryBean} for a {@link TaskBatchExecutionListener}. Provides a jdbc based @@ -41,19 +38,15 @@ import org.springframework.util.StringUtils; */ public class TaskBatchExecutionListenerFactoryBean implements FactoryBean { - private ConfigurableApplicationContext context; - private TaskBatchExecutionListener listener; - private String dataSourceName; + private DataSource dataSource; - /** - * @param context the current application context - */ - public TaskBatchExecutionListenerFactoryBean(ConfigurableApplicationContext context) { - Assert.notNull(context, "A ConfigurableApplicationContext is required"); + private TaskExplorer taskExplorer; - this.context = context; + public TaskBatchExecutionListenerFactoryBean(DataSource dataSource, TaskExplorer taskExplorer) { + this.dataSource = dataSource; + this.taskExplorer = taskExplorer; } @Override @@ -61,24 +54,11 @@ public class TaskBatchExecutionListenerFactoryBean implements FactoryBean page = taskExplorer.findTaskExecutionsByName("application", new PageRequest(0, 1)); + + Set jobExecutionIds = taskExplorer.getJobExecutionIdsByTaskExecutionId(page.iterator().next().getExecutionId()); + + assertEquals(1, jobExecutionIds.size()); + assertEquals(1, taskExplorer.getTaskExecution(jobExecutionIds.iterator().next()).getExecutionId()); + } + + @Test + public void testMultipleDataSources() { + this.applicationContext = SpringApplication.run(new Object[] {JobConfigurationMultipleDataSources.class, + PropertyPlaceholderAutoConfiguration.class, + EmbeddedDataSourceConfiguration.class, + BatchAutoConfiguration.class, + TaskBatchAutoConfiguration.class}, new String[0]); TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class); @@ -76,7 +108,11 @@ public class BatchTaskExecutionListenerTests { @Test public void testAutobuiltDataSourceNoJob() { - this.applicationContext = SpringApplication.run(new Object[] {NoJobConfiguration.class, PropertyPlaceholderAutoConfiguration.class, EmbeddedDataSourceConfiguration.class, TaskBatchAutoConfiguration.class, BatchAutoConfiguration.class, TaskBatchAutoConfiguration.class}, new String[0]); + this.applicationContext = SpringApplication.run(new Object[] {NoJobConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, + EmbeddedDataSourceConfiguration.class, + BatchAutoConfiguration.class, + TaskBatchAutoConfiguration.class}, new String[0]); TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class); @@ -89,7 +125,10 @@ public class BatchTaskExecutionListenerTests { @Test public void testMapBased() { - this.applicationContext = SpringApplication.run(new Object[] {JobConfiguration.class, PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class, TaskBatchAutoConfiguration.class}, new String[0]); + this.applicationContext = SpringApplication.run(new Object[] {JobConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, + BatchAutoConfiguration.class, + TaskBatchAutoConfiguration.class}, new String[0]); TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class); @@ -103,7 +142,10 @@ public class BatchTaskExecutionListenerTests { @Test public void testMultipleJobs() { - this.applicationContext = SpringApplication.run(new Object[] {MultipleJobConfiguration.class, PropertyPlaceholderAutoConfiguration.class, BatchAutoConfiguration.class, TaskBatchAutoConfiguration.class}, new String[0]); + this.applicationContext = SpringApplication.run(new Object[] {MultipleJobConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, + BatchAutoConfiguration.class, + TaskBatchAutoConfiguration.class}, new String[0]); TaskExplorer taskExplorer = this.applicationContext.getBean(TaskExplorer.class); @@ -149,6 +191,72 @@ public class BatchTaskExecutionListenerTests { } } + @Configuration + @EnableBatchProcessing + @EnableTask + public static class JobConfigurationMultipleDataSources { + + @Autowired + private JobBuilderFactory jobBuilderFactory; + + @Autowired + private StepBuilderFactory stepBuilderFactory; + + @Bean + public Job job() { + return jobBuilderFactory.get("job") + .start(stepBuilderFactory.get("step1").tasklet(new Tasklet() { + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + System.out.println("Executed"); + return RepeatStatus.FINISHED; + } + }).build()) + .build(); + } + + @Bean + @Primary + public DataSource myDataSource() { + EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.H2) + .setName("myDataSource"); + return builder.build(); + } + + @Bean + public DataSource incorrectDataSource() { + EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.H2) + .setName("incorrectDataSource"); + return builder.build(); + } + + @Bean + public TaskBatchExecutionListenerFactoryBean taskBatchExecutionListener(TaskExplorer taskExplorer) { + return new TaskBatchExecutionListenerFactoryBean(myDataSource(), taskExplorer); + } + + @Bean + public TaskConfigurer taskConfigurer() { + return new DefaultTaskConfigurer(myDataSource()); + } + + @Bean + public TaskRepositoryInitializer taskRepositoryInitializer() { + TaskRepositoryInitializer taskRepositoryInitializer = new TaskRepositoryInitializer(); + + taskRepositoryInitializer.setDataSource(myDataSource()); + + return taskRepositoryInitializer; + } + + @Bean + public DefaultBatchConfigurer batchConfigurer() { + return new DefaultBatchConfigurer(myDataSource()); + } + } + @Configuration @EnableBatchProcessing @EnableTask diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/DefaultTaskConfigurer.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/DefaultTaskConfigurer.java index 066773b1..1fad9f5f 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/DefaultTaskConfigurer.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/DefaultTaskConfigurer.java @@ -26,7 +26,6 @@ import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao; import org.springframework.cloud.task.repository.support.SimpleTaskExplorer; import org.springframework.cloud.task.repository.support.SimpleTaskRepository; import org.springframework.cloud.task.repository.support.TaskExecutionDaoFactoryBean; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; @@ -51,14 +50,25 @@ public class DefaultTaskConfigurer implements TaskConfigurer { private PlatformTransactionManager transactionManager; - private ConfigurableApplicationContext context; - private TaskExecutionDaoFactoryBean taskExecutionDaoFactoryBean; - public DefaultTaskConfigurer(ConfigurableApplicationContext context) { - this.context = context; + private DataSource dataSource; + + /** + * @param dataSource references the {@link DataSource} to be used as the Task + * repository. If none is provided, a Map will be used (not recommended for + * production use. + */ + public DefaultTaskConfigurer(DataSource dataSource) { + this.dataSource = dataSource; + + if(this.dataSource != null) { + this.taskExecutionDaoFactoryBean = new TaskExecutionDaoFactoryBean(this.dataSource); + } + else { + this.taskExecutionDaoFactoryBean = new TaskExecutionDaoFactoryBean(); + } - this.taskExecutionDaoFactoryBean = new TaskExecutionDaoFactoryBean(this.context); this.taskRepository = new SimpleTaskRepository(this.taskExecutionDaoFactoryBean); this.taskExplorer = new SimpleTaskExplorer(this.taskExecutionDaoFactoryBean); } @@ -77,7 +87,7 @@ public class DefaultTaskConfigurer implements TaskConfigurer { public PlatformTransactionManager getTransactionManager() { if(this.transactionManager == null) { if(isDataSourceAvailable()) { - this.transactionManager = new DataSourceTransactionManager(this.context.getBean(DataSource.class)); + this.transactionManager = new DataSourceTransactionManager(this.dataSource); } else { this.transactionManager = new ResourcelessTransactionManager(); @@ -88,6 +98,6 @@ public class DefaultTaskConfigurer implements TaskConfigurer { } private boolean isDataSourceAvailable() { - return this.context.getBeanNamesForType(DataSource.class).length == 1; + return this.dataSource != null; } } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskConfiguration.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskConfiguration.java index 36d121b4..a1fd8208 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskConfiguration.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskConfiguration.java @@ -17,7 +17,6 @@ package org.springframework.cloud.task.configuration; import java.util.Collection; - import javax.annotation.PostConstruct; import javax.sql.DataSource; @@ -27,8 +26,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.cloud.task.listener.TaskLifecycleListener; -import org.springframework.cloud.task.listener.annotation.TaskListenerExecutor; -import org.springframework.cloud.task.listener.annotation.TaskListenerExecutorFactory; +import org.springframework.cloud.task.listener.annotation.TaskListenerExecutorFactoryBean; import org.springframework.cloud.task.repository.TaskExplorer; import org.springframework.cloud.task.repository.TaskNameResolver; import org.springframework.cloud.task.repository.TaskRepository; @@ -39,6 +37,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.util.CollectionUtils; /** * Base {@code Configuration} class providing common structure for enabling and using @@ -54,6 +53,9 @@ public class SimpleTaskConfiguration { protected static final Log logger = LogFactory.getLog(SimpleTaskConfiguration.class); + @Autowired(required = false) + private Collection dataSources; + @Autowired private ConfigurableApplicationContext context; @@ -62,34 +64,40 @@ public class SimpleTaskConfiguration { private boolean initialized = false; - private TaskConfigurer configurer; + private TaskRepository taskRepository; + + private TaskLifecycleListener taskLifecycleListener; + + private TaskListenerExecutorFactoryBean taskListenerExecutorFactoryBean; + + private PlatformTransactionManager platformTransactionManager; + + private TaskExplorer taskExplorer; @Bean public TaskRepository taskRepository(){ - return this.configurer.getTaskRepository(); + return this.taskRepository; } @Bean public TaskLifecycleListener taskLifecycleListener() { - return new TaskLifecycleListener(taskRepository(), taskNameResolver(), this.applicationArguments); + return this.taskLifecycleListener; } @Bean - public TaskListenerExecutor taskListenerExecutor(ConfigurableApplicationContext context) throws Exception - { - TaskListenerExecutorFactory taskListenerExecutorFactory = - new TaskListenerExecutorFactory(context); - return taskListenerExecutorFactory.getObject(); + public TaskListenerExecutorFactoryBean taskListenerExecutor() + throws Exception { + return this.taskListenerExecutorFactoryBean; } @Bean public PlatformTransactionManager transactionManager() { - return this.configurer.getTransactionManager(); + return this.platformTransactionManager; } @Bean public TaskExplorer taskExplorer() { - return this.configurer.getTaskExplorer(); + return this.taskExplorer; } @Bean @@ -101,8 +109,9 @@ public class SimpleTaskConfiguration { public TaskRepositoryInitializer taskRepositoryInitializer() { TaskRepositoryInitializer taskRepositoryInitializer = new TaskRepositoryInitializer(); - if(this.context.getBeanNamesForType(DataSource.class).length == 1) { - taskRepositoryInitializer.setDataSource(context.getBean(DataSource.class)); + if(!CollectionUtils.isEmpty(this.dataSources) && this.dataSources.size() == 1) { + DataSource next = this.dataSources.iterator().next(); + taskRepositoryInitializer.setDataSource(next); } return taskRepositoryInitializer; @@ -112,40 +121,56 @@ public class SimpleTaskConfiguration { * Determines the {@link TaskConfigurer} to use. */ @PostConstruct - protected void initialize() { + protected void initialize() throws Exception { if (initialized) { return; } - logger.debug("Getting Task Configurer"); - if (configurer == null) { - configurer = getDefaultConfigurer(context.getBeansOfType(TaskConfigurer.class).values()); - } + + TaskConfigurer taskConfigurer = getDefaultConfigurer(); + logger.debug(String.format("Using %s TaskConfigurer", - configurer.getClass().getName())); + taskConfigurer.getClass().getName())); + + this.taskRepository = taskConfigurer.getTaskRepository(); + this.taskListenerExecutorFactoryBean = new TaskListenerExecutorFactoryBean(context); + this.platformTransactionManager = taskConfigurer.getTransactionManager(); + this.taskExplorer = taskConfigurer.getTaskExplorer(); + + this.taskLifecycleListener = new TaskLifecycleListener(this.taskRepository, taskNameResolver(), this.applicationArguments); + initialized = true; } - private TaskConfigurer getDefaultConfigurer(Collection configurers) { - verifyEnvironment(configurers); - if (configurers == null || configurers.isEmpty()) { - this.configurer = new DefaultTaskConfigurer(this.context); - return this.configurer; + private TaskConfigurer getDefaultConfigurer() { + verifyEnvironment(); + + int configurers = this.context.getBeanNamesForType(TaskConfigurer.class).length; + + if (configurers < 1) { + if(!CollectionUtils.isEmpty(this.dataSources) && this.dataSources.size() == 1) { + return new DefaultTaskConfigurer(this.dataSources.iterator().next()); + } + else { + return new DefaultTaskConfigurer(null); + } + } + else { + if(configurers == 1) { + return this.context.getBean(TaskConfigurer.class); + } + else { + throw new IllegalStateException("Expected one TaskConfigurer but found " + configurers); + } } - this.configurer = configurers.iterator().next(); - return this.configurer; } - private void verifyEnvironment(Collection configurers){ + private void verifyEnvironment(){ + int configurers = this.context.getBeanNamesForType(TaskConfigurer.class).length; int dataSources = this.context.getBeanNamesForType(DataSource.class).length; - if (dataSources > 1) { + if(configurers == 0 && dataSources > 1) { throw new IllegalStateException("To use the default TaskConfigurer the context must contain no more than" + - "one DataSource, found " + dataSources); - } - if (configurers.size() > 1) { - throw new IllegalStateException( - "To use a custom TaskConfigurer the context must contain precisely one, found " - + configurers.size()); + " one DataSource, found " + dataSources); } } } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java index 3ca41db0..b2e75b8b 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 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. @@ -36,6 +36,7 @@ import org.springframework.cloud.task.repository.TaskNameResolver; import org.springframework.cloud.task.repository.TaskRepository; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; +import org.springframework.context.SmartLifecycle; import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.util.Assert; @@ -58,7 +59,7 @@ import org.springframework.util.Assert; * * @author Michael Minella */ -public class TaskLifecycleListener implements ApplicationListener{ +public class TaskLifecycleListener implements ApplicationListener, SmartLifecycle { @Autowired(required = false) private Collection taskExecutionListeners; @@ -106,11 +107,7 @@ public class TaskLifecycleListener implements ApplicationListener { +public class TaskListenerExecutorFactoryBean implements FactoryBean { private final static Log logger = LogFactory.getLog(TaskListenerExecutor.class); @@ -55,7 +56,7 @@ public class TaskListenerExecutorFactory implements FactoryBean failedTaskInstances; - public TaskListenerExecutorFactory(ConfigurableApplicationContext context){ + public TaskListenerExecutorFactoryBean(ConfigurableApplicationContext context){ this.context = context; } @@ -161,5 +162,4 @@ public class TaskListenerExecutorFactory implements FactoryBean targetClass = AopProxyUtils.ultimateTargetClass(taskRepository); + + assertEquals(targetClass, SimpleTaskRepository.class); + } + + @Test(expected = BeanCreationException.class) + public void testMultipleConfigurers() { + this.context = new AnnotationConfigApplicationContext(MultipleConfigurers.class, + PropertyPlaceholderAutoConfiguration.class); + } + + @Configuration + @EnableTask + public static class MultipleConfigurers { + + @Bean + public TaskConfigurer taskConfigurer1() { + return new DefaultTaskConfigurer(null); + } + + @Bean + public TaskConfigurer taskConfigurer2() { + return new DefaultTaskConfigurer(null); + } + } + +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/configuration/TestConfiguration.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/configuration/TestConfiguration.java index 3c056d7a..bad33af1 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/configuration/TestConfiguration.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/configuration/TestConfiguration.java @@ -26,7 +26,6 @@ import org.springframework.cloud.task.repository.support.SimpleTaskExplorer; import org.springframework.cloud.task.repository.support.SimpleTaskRepository; import org.springframework.cloud.task.repository.support.TaskExecutionDaoFactoryBean; import org.springframework.cloud.task.repository.support.TaskRepositoryInitializer; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ResourceLoader; @@ -46,9 +45,6 @@ public class TestConfiguration implements InitializingBean { @Autowired(required = false) private ResourceLoader resourceLoader; - @Autowired - private ConfigurableApplicationContext applicationContext; - private TaskExecutionDaoFactoryBean taskExecutionDaoFactoryBean; @Bean @@ -83,6 +79,11 @@ public class TestConfiguration implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { - this.taskExecutionDaoFactoryBean = new TaskExecutionDaoFactoryBean(this.applicationContext); + if(this.dataSource != null) { + this.taskExecutionDaoFactoryBean = new TaskExecutionDaoFactoryBean(this.dataSource); + } + else { + this.taskExecutionDaoFactoryBean = new TaskExecutionDaoFactoryBean(); + } } } diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/listener/TaskExecutionListenerTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/listener/TaskExecutionListenerTests.java index c292d8a8..9900860d 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/listener/TaskExecutionListenerTests.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/listener/TaskExecutionListenerTests.java @@ -16,24 +16,19 @@ package org.springframework.cloud.task.listener; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - import java.util.ArrayList; import java.util.Date; import org.junit.After; import org.junit.Test; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.cloud.task.listener.annotation.AfterTask; import org.springframework.cloud.task.listener.annotation.BeforeTask; import org.springframework.cloud.task.listener.annotation.FailedTask; -import org.springframework.cloud.task.listener.annotation.TaskListenerExecutor; -import org.springframework.cloud.task.listener.annotation.TaskListenerExecutorFactory; +import org.springframework.cloud.task.listener.annotation.TaskListenerExecutorFactoryBean; import org.springframework.cloud.task.repository.TaskExecution; import org.springframework.cloud.task.util.TestDefaultConfiguration; import org.springframework.cloud.task.util.TestListener; @@ -43,6 +38,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.ContextClosedEvent; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + /** * Verifies that the TaskExecutionListener invocations occur at the appropriate task * lifecycle stages. @@ -203,10 +203,9 @@ public class TaskExecutionListenerTests { } @Bean - public TaskListenerExecutor taskListenerExecutor(ConfigurableApplicationContext context) throws Exception + public TaskListenerExecutorFactoryBean taskListenerExecutor(ConfigurableApplicationContext context) throws Exception { - TaskListenerExecutorFactory taskListenerExecutorFactory = new TaskListenerExecutorFactory(context); - return taskListenerExecutorFactory.getObject(); + return new TaskListenerExecutorFactoryBean(context); } public static class AnnotatedTaskListener extends TestListener { diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryMapTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryMapTests.java index 0eb6b0a4..7f465f7d 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryMapTests.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/SimpleTaskRepositoryMapTests.java @@ -27,9 +27,6 @@ import org.springframework.cloud.task.repository.TaskRepository; import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao; import org.springframework.cloud.task.util.TaskExecutionCreator; import org.springframework.cloud.task.util.TestVerifierUtils; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Configuration; import static org.springframework.test.util.AssertionErrors.assertTrue; @@ -43,8 +40,7 @@ public class SimpleTaskRepositoryMapTests { @Before public void setUp() { - ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(EmptyConfiguration.class); - this.taskRepository = new SimpleTaskRepository(new TaskExecutionDaoFactoryBean(context)); + this.taskRepository = new SimpleTaskRepository(new TaskExecutionDaoFactoryBean()); } @Test @@ -52,8 +48,7 @@ public class SimpleTaskRepositoryMapTests { TaskExecution expectedTaskExecution = TaskExecutionCreator.createAndStoreTaskExecutionNoParams(taskRepository); TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, - getSingleTaskExecutionFromMapRepository(taskRepository, - expectedTaskExecution.getExecutionId())); + getSingleTaskExecutionFromMapRepository(expectedTaskExecution.getExecutionId())); } @Test @@ -61,8 +56,7 @@ public class SimpleTaskRepositoryMapTests { TaskExecution expectedTaskExecution = TaskExecutionCreator.createAndStoreTaskExecutionWithParams(taskRepository); TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, - getSingleTaskExecutionFromMapRepository(taskRepository, - expectedTaskExecution.getExecutionId())); + getSingleTaskExecutionFromMapRepository(expectedTaskExecution.getExecutionId())); } @Test @@ -75,8 +69,7 @@ public class SimpleTaskRepositoryMapTests { TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, actualTaskExecution); } - private TaskExecution getSingleTaskExecutionFromMapRepository( - TaskRepository repository, long taskExecutionId){ + private TaskExecution getSingleTaskExecutionFromMapRepository(long taskExecutionId){ Map taskMap = ((MapTaskExecutionDao) ((SimpleTaskRepository)taskRepository).getTaskExecutionDao()).getTaskExecutions(); assertTrue("taskExecutionId must be in MapTaskExecutionRepository", @@ -91,7 +84,4 @@ public class SimpleTaskRepositoryMapTests { expectedTaskExecution.setExitCode(-1); TaskExecutionCreator.completeExecution(taskRepository, expectedTaskExecution); } - - @Configuration - public static class EmptyConfiguration{} } diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskDatabaseInitializerTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskDatabaseInitializerTests.java index 800d2403..2227b59f 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskDatabaseInitializerTests.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskDatabaseInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -16,11 +16,6 @@ package org.springframework.cloud.task.repository.support; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; - import javax.sql.DataSource; import org.junit.After; @@ -38,6 +33,11 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + /** * Verifies that task initialization occurs properly. * @@ -70,7 +70,7 @@ public class TaskDatabaseInitializerTests { @Test public void testNoDatabase() throws Exception { this.context = new AnnotationConfigApplicationContext(EmptyConfiguration.class); - SimpleTaskRepository repository = new SimpleTaskRepository(new TaskExecutionDaoFactoryBean(this.context)); + SimpleTaskRepository repository = new SimpleTaskRepository(new TaskExecutionDaoFactoryBean()); assertThat(repository.getTaskExecutionDao(), instanceOf(MapTaskExecutionDao.class)); MapTaskExecutionDao dao = (MapTaskExecutionDao) repository.getTaskExecutionDao(); assertEquals(0, dao.getTaskExecutions().size()); diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskExecutionDaoFactoryBeanTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskExecutionDaoFactoryBeanTests.java index 7fcb1383..941cd6ba 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskExecutionDaoFactoryBeanTests.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/repository/support/TaskExecutionDaoFactoryBeanTests.java @@ -15,9 +15,6 @@ */ package org.springframework.cloud.task.repository.support; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import javax.sql.DataSource; import org.junit.After; @@ -30,11 +27,13 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.support.GenericApplicationContext; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.test.util.ReflectionTestUtils; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + /** * @author Michael Minella */ @@ -64,20 +63,6 @@ public class TaskExecutionDaoFactoryBeanTests { new TaskExecutionDaoFactoryBean(null); } - @Test - public void testMapTaskExecutionDaoWithAppContext() throws Exception { - this.context = new GenericApplicationContext(); - this.context.refresh(); - - TaskExecutionDaoFactoryBean factoryBean = new TaskExecutionDaoFactoryBean(this.context); - TaskExecutionDao taskExecutionDao = factoryBean.getObject(); - - assertTrue(taskExecutionDao instanceof MapTaskExecutionDao); - - TaskExecutionDao taskExecutionDao2 = factoryBean.getObject(); - - assertTrue(taskExecutionDao == taskExecutionDao2); - } @Test public void testMapTaskExecutionDaoWithoutAppContext() throws Exception { @@ -95,7 +80,9 @@ public class TaskExecutionDaoFactoryBeanTests { public void testDefaultDataSourceConfiguration() throws Exception { this.context = new AnnotationConfigApplicationContext(DefaultDataSourceConfiguration.class); - TaskExecutionDaoFactoryBean factoryBean = new TaskExecutionDaoFactoryBean(this.context); + DataSource dataSource = this.context.getBean(DataSource.class); + + TaskExecutionDaoFactoryBean factoryBean = new TaskExecutionDaoFactoryBean(dataSource); TaskExecutionDao taskExecutionDao = factoryBean.getObject(); assertTrue(taskExecutionDao instanceof JdbcTaskExecutionDao); @@ -105,66 +92,14 @@ public class TaskExecutionDaoFactoryBeanTests { assertTrue(taskExecutionDao == taskExecutionDao2); } - @Test - public void testNonDefaultNameDataSourceConfiguration() throws Exception { - this.context = new AnnotationConfigApplicationContext(AlternativeDataSourceConfiguration.class); - - TaskExecutionDaoFactoryBean factoryBean = new TaskExecutionDaoFactoryBean(this.context); - TaskExecutionDao taskExecutionDao = factoryBean.getObject(); - - assertTrue(taskExecutionDao instanceof JdbcTaskExecutionDao); - - TaskExecutionDao taskExecutionDao2 = factoryBean.getObject(); - - assertTrue(taskExecutionDao == taskExecutionDao2); - } - - @Test(expected = IllegalArgumentException.class) - public void testMissingCustomDataSourceNameConfiguration() throws Exception { - ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(AlternativeDataSourceConfiguration.class); - - TaskExecutionDaoFactoryBean factoryBean = new TaskExecutionDaoFactoryBean(context); - factoryBean.setDataSourceName("wrongName"); - factoryBean.getObject(); - } - - @Test - public void testCustomDataSourceNameConfiguration() throws Exception { - this.context = new AnnotationConfigApplicationContext(AlternativeDataSourceConfiguration.class); - - TaskExecutionDaoFactoryBean factoryBean = new TaskExecutionDaoFactoryBean(this.context); - factoryBean.setDataSourceName("notDataSource"); - TaskExecutionDao taskExecutionDao = factoryBean.getObject(); - - assertTrue(taskExecutionDao instanceof JdbcTaskExecutionDao); - - TaskExecutionDao taskExecutionDao2 = factoryBean.getObject(); - - assertTrue(taskExecutionDao == taskExecutionDao2); - } - - @Test - public void testCustomDataSourceNameConfigurationWithMultipleDataSources() throws Exception { - this.context = new AnnotationConfigApplicationContext(MultipleDataSourceConfiguration.class); - - TaskExecutionDaoFactoryBean factoryBean = new TaskExecutionDaoFactoryBean(this.context); - factoryBean.setDataSourceName("useThisDataSource"); - JdbcTaskExecutionDao taskExecutionDao = (JdbcTaskExecutionDao) factoryBean.getObject(); - - Object usedDataSource = ReflectionTestUtils.getField(taskExecutionDao, "dataSource"); - - assertTrue(usedDataSource == this.context.getBean("useThisDataSource")); - - TaskExecutionDao taskExecutionDao2 = factoryBean.getObject(); - - assertTrue(taskExecutionDao == taskExecutionDao2); - } @Test public void testSettingTablePrefix() throws Exception { this.context = new AnnotationConfigApplicationContext(DefaultDataSourceConfiguration.class); - TaskExecutionDaoFactoryBean factoryBean = new TaskExecutionDaoFactoryBean(this.context); + DataSource dataSource = this.context.getBean(DataSource.class); + + TaskExecutionDaoFactoryBean factoryBean = new TaskExecutionDaoFactoryBean(dataSource); factoryBean.setTablePrefix("foo_"); TaskExecutionDao taskExecutionDao = factoryBean.getObject(); @@ -180,35 +115,4 @@ public class TaskExecutionDaoFactoryBeanTests { return builder.build(); } } - - @Configuration - public static class AlternativeDataSourceConfiguration { - - @Bean - public DataSource notDataSource() { - EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2); - return builder.build(); - } - } - - @Configuration - public static class MultipleDataSourceConfiguration { - - @Bean - public DataSource useThisDataSource() { - EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder() - .setType(EmbeddedDatabaseType.H2) - .setName("useThisDataSource"); - return builder.build(); - } - - @Bean - public DataSource dontUseThisDataSource() { - EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder() - .setType(EmbeddedDatabaseType.H2) - .setName("dontUseThisDataSource"); - return builder.build(); - } - - } } diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultConfiguration.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultConfiguration.java index 79aeabb4..616d2ca3 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultConfiguration.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -16,6 +16,8 @@ package org.springframework.cloud.task.util; +import javax.sql.DataSource; + import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; @@ -35,6 +37,7 @@ import org.springframework.context.annotation.Configuration; * Initializes the beans needed to test default task behavior. * * @author Glenn Renfro + * @author Michael Minella */ @Configuration public class TestDefaultConfiguration implements InitializingBean { @@ -72,6 +75,12 @@ public class TestDefaultConfiguration implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { - this.factoryBean = new TaskExecutionDaoFactoryBean(this.context); + if(this.context.getBeanNamesForType(DataSource.class).length == 1){ + DataSource dataSource = this.context.getBean(DataSource.class); + this.factoryBean = new TaskExecutionDaoFactoryBean(dataSource); + } + else { + this.factoryBean = new TaskExecutionDaoFactoryBean(); + } } } diff --git a/spring-cloud-task-docs/src/main/asciidoc/features.adoc b/spring-cloud-task-docs/src/main/asciidoc/features.adoc index 974938b6..cf5780d8 100644 --- a/spring-cloud-task-docs/src/main/asciidoc/features.adoc +++ b/spring-cloud-task-docs/src/main/asciidoc/features.adoc @@ -33,13 +33,10 @@ Spring Boot application configured to be a task (annotated with the `@EnableTask annotation). At the beginning of a task, an entry in the `TaskRepository` is created recording the -start event. This event is triggered via the `ContextRefreshEvent` being triggered by -Spring Framework. +start event. This event is triggered via `SmartLifecycle#start` being triggered by +Spring Framework. This indicates to the system that all beans are ready for use and is +before the execution of any of the `*Runner`s provided by Spring Boot. -NOTE: As Spring Cloud Task is expected to consist of a single application context. If -multiple application contexts are used (parent/child relationships for example), the first -`ContextRefreshEvent` that is published by Spring will be recorded as the start of the -task. NOTE: The recording of a task will only occur upon the successful bootstrapping of an `ApplicationContext`. If the context fails to bootstrap at all, the task's execution will @@ -70,7 +67,7 @@ assumed to be 0. |The name for the task as determined by the configured `TaskNameResolver`. |`starTime` -|The time the task was started as indicated by the `ContextRefreshEvent`. +|The time the task was started as indicated by the `SmartLifecycle#start` call. |`endTime` |The time the task was completed as indicated by the `ContextClosedEvent`.