Refactor of Task API

This change removes the Spring Cloud Task dependency upon a
CommandLineRunner from boot and moves the handling of the task lifecycle
closer to the "edge" of a Spring Boot application.

With this change, now a developer simply adds @EnableTask to their
configuration somewhere and the task lifecycle will be recorded.

Resolves spring-cloud/spring-cloud-task#39
This commit is contained in:
Michael Minella
2015-12-15 13:00:42 -06:00
committed by Glenn Renfro
parent 7be3a88826
commit 61d3cc3641
23 changed files with 453 additions and 465 deletions

View File

@@ -1,87 +0,0 @@
/*
* Copyright 2015 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.cloud.task;
import static org.junit.Assert.assertEquals;
import ch.qos.logback.core.Appender;
import org.aspectj.lang.JoinPoint;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.cloud.task.configuration.TaskHandler;
import org.springframework.cloud.task.repository.TaskExecution;
import org.springframework.cloud.task.util.TestDefaultConfiguration;
import org.springframework.cloud.task.util.TestVerifierUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Verifies that the TaskHandler Methods record the appropriate log header entries and
* result codes.
*
* @author Glenn Renfro
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestDefaultConfiguration.class, PropertyPlaceholderAutoConfiguration.class})
public class TaskHandlerDefaultTests {
@Autowired
private TaskHandler taskHandler;
@Autowired
private JoinPoint joinPoint;
@Test
public void testTaskException() {
taskHandler.beforeCommandLineRunner(joinPoint);
final Appender mockAppender = TestVerifierUtils.getMockAppender();
taskHandler.logExceptionCommandLineRunner(joinPoint);
TestVerifierUtils.verifyLogEntryExists(mockAppender,
"Updating: TaskExecution{executionId='" +
taskHandler.getTaskExecution().getExecutionId());
TaskExecution taskExecution = taskHandler.getTaskExecution();
assertEquals("exit code for exception should be 1", taskExecution.getExitCode(),
1);
}
@Test
public void testTaskCreate() {
final Appender mockAppender = TestVerifierUtils.getMockAppender();
taskHandler.beforeCommandLineRunner(joinPoint);
TestVerifierUtils.verifyLogEntryExists(mockAppender,
"Creating: TaskExecution{executionId='" +
taskHandler.getTaskExecution().getExecutionId());
assertEquals("Create should report that exit code is zero",
0, taskHandler.getTaskExecution().getExitCode());
}
@Test
public void testTaskUpdate() {
taskHandler.beforeCommandLineRunner(joinPoint);
final Appender mockAppender = TestVerifierUtils.getMockAppender();
taskHandler.afterReturnCommandLineRunner(joinPoint);
TestVerifierUtils.verifyLogEntryExists(mockAppender,
"Updating: TaskExecution{executionId='" +
taskHandler.getTaskExecution().getExecutionId());
assertEquals("Update should report that exit code is zero",
0, taskHandler.getTaskExecution().getExitCode());
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2015 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.cloud.task.configuration;
import javax.sql.DataSource;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.task.repository.TaskExplorer;
import org.springframework.cloud.task.repository.TaskRepository;
import org.springframework.cloud.task.repository.dao.JdbcTaskExecutionDao;
import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao;
import org.springframework.cloud.task.repository.dao.TaskExecutionDao;
import org.springframework.cloud.task.repository.support.SimpleTaskExplorer;
import org.springframework.cloud.task.repository.support.SimpleTaskRepository;
import org.springframework.cloud.task.repository.support.TaskDatabaseInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
/**
* @author Michael Minella
*/
@Configuration
public class TestConfiguration {
@Autowired(required = false)
private DataSource dataSource;
@Autowired(required = false)
private ResourceLoader resourceLoader;
@Bean
public TaskRepository taskRepository(TaskExecutionDao taskExecutionDao){
return new SimpleTaskRepository(taskExecutionDao);
}
@Bean
public PlatformTransactionManager transactionManager() {
if(dataSource == null) {
return new ResourcelessTransactionManager();
}
else {
TaskDatabaseInitializer.initializeDatabase(dataSource, this.resourceLoader);
return new DataSourceTransactionManager(dataSource);
}
}
@Bean
public TaskExplorer taskExplorer(TaskExecutionDao taskExecutionDao) {
return new SimpleTaskExplorer(taskExecutionDao);
}
@Bean
public TaskExecutionDao taskExecutionDao() {
if(dataSource != null) {
return new JdbcTaskExecutionDao(dataSource);
}
else {
return new MapTaskExecutionDao();
}
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2015 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.cloud.task.listener;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import ch.qos.logback.core.Appender;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.cloud.task.util.TestDefaultConfiguration;
import org.springframework.cloud.task.util.TestVerifierUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
/**
* Verifies that the TaskLifecycleListener Methods record the appropriate log header entries and
* result codes.
*
* @author Glenn Renfro
* @author Michael Minella
*/
public class TaskLifecycleListenerTests {
@Autowired
private TaskLifecycleListener listener;
private AnnotationConfigApplicationContext context;
@Before
public void setUp() {
context = new AnnotationConfigApplicationContext();
context.register(TestDefaultConfiguration.class, PropertyPlaceholderAutoConfiguration.class);
}
@After
public void tearDown() {
context.close();
}
@Test
public void testTaskCreate() {
final Appender mockAppender = TestVerifierUtils.getMockAppender();
context.refresh();
this.listener = context.getBean(TaskLifecycleListener.class);
TestVerifierUtils.verifyLogEntryExists(mockAppender,
"Creating: TaskExecution{executionId='" +
listener.getTaskExecution().getExecutionId());
assertEquals("Create should report that exit code is zero",
0, listener.getTaskExecution().getExitCode());
}
@Test
public void testTaskUpdate() {
context.refresh();
final Appender mockAppender = TestVerifierUtils.getMockAppender();
this.listener = context.getBean(TaskLifecycleListener.class);
this.listener.onApplicationEvent(new ContextClosedEvent(context));
TestVerifierUtils.verifyLogEntryExists(mockAppender,
"Updating: TaskExecution{executionId='" +
listener.getTaskExecution().getExecutionId());
assertEquals("Update should report that exit code is zero",
0, listener.getTaskExecution().getExitCode());
}
@Test
public void testTaskFailedUpdate() {
context.refresh();
final Appender mockAppender = TestVerifierUtils.getMockAppender();
this.listener = context.getBean(TaskLifecycleListener.class);
listener.onApplicationEvent(new ApplicationFailedEvent(new SpringApplication(), new String[]{}, context, new RuntimeException("This was expected")));
TestVerifierUtils.verifyLogEntryExists(mockAppender,
"Updating: TaskExecution{executionId='" +
listener.getTaskExecution().getExecutionId());
assertEquals("Update should report that exit code is one",
1, listener.getTaskExecution().getExitCode());
assertTrue("Stack trace missing from exit message", listener.getTaskExecution().getExitMessage().startsWith("java.lang.RuntimeException: This was expected"));
}
}

View File

@@ -18,47 +18,37 @@ package org.springframework.cloud.task.repository.dao;
import javax.sql.DataSource;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.cloud.task.annotation.EnableTask;
import org.springframework.cloud.task.configuration.TestConfiguration;
import org.springframework.cloud.task.repository.TaskExecution;
import org.springframework.cloud.task.util.TestDBUtils;
import org.springframework.cloud.task.util.TestVerifierUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Executes unit tests on JdbcTaskExecutionDao.
*
* @author Glenn Renfro
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestConfiguration.class,
EmbeddedDataSourceConfiguration.class,
PropertyPlaceholderAutoConfiguration.class})
public class JdbcTaskExecutionDaoTests {
@Autowired
private DataSource dataSource;
private AnnotationConfigApplicationContext context;
@Before
public void setup() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(TestConfiguration.class,
EmbeddedDataSourceConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
dataSource = this.context.getBean(DataSource.class);
}
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
@DirtiesContext
public void saveTaskExecution() {
JdbcTaskExecutionDao dao = new JdbcTaskExecutionDao(dataSource);
TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam();
@@ -69,6 +59,7 @@ public class JdbcTaskExecutionDaoTests {
}
@Test(expected = DuplicateKeyException.class)
@DirtiesContext
public void duplicateSaveTaskExecution() {
JdbcTaskExecutionDao dao = new JdbcTaskExecutionDao(dataSource);
TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam();
@@ -77,6 +68,7 @@ public class JdbcTaskExecutionDaoTests {
}
@Test
@DirtiesContext
public void updateTaskExecution() {
JdbcTaskExecutionDao dao = new JdbcTaskExecutionDao(dataSource);
@@ -88,14 +80,11 @@ public class JdbcTaskExecutionDaoTests {
}
@Test(expected = IllegalStateException.class)
@DirtiesContext
public void updateTaskExecutionWithNoCreate() {
JdbcTaskExecutionDao dao = new JdbcTaskExecutionDao(dataSource);
TaskExecution expectedTaskExecution = TestVerifierUtils.createSampleTaskExecutionNoParam();
dao.updateTaskExecution(expectedTaskExecution);
}
@EnableTask
protected static class TestConfiguration {
}
}

View File

@@ -40,16 +40,18 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.cloud.task.annotation.EnableTask;
import org.springframework.cloud.task.configuration.TestConfiguration;
import org.springframework.cloud.task.repository.TaskExecution;
import org.springframework.cloud.task.repository.TaskExplorer;
import org.springframework.cloud.task.repository.dao.JdbcTaskExecutionDao;
import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao;
import org.springframework.cloud.task.repository.dao.TaskExecutionDao;
import org.springframework.cloud.task.util.TestVerifierUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.io.ResourceLoader;
/**
* @author Glenn Renfro
@@ -59,12 +61,18 @@ public class SimpleTaskExplorerTests {
private AnnotationConfigApplicationContext context;
@Autowired(required = false)
private DataSource dataSource;
@Autowired
private TaskExecutionDao dao;
@Autowired
private TaskExplorer taskExplorer;
@Autowired
private ResourceLoader resourceLoader;
private DaoType testType;
@Parameterized.Parameters
@@ -84,9 +92,11 @@ public class SimpleTaskExplorerTests {
if(testType == DaoType.jdbc){
initializeJdbcExplorerTest();
}else{
}
else{
initializeMapExplorerTest();
}
taskExplorer = new SimpleTaskExplorer(dao);
}
@@ -242,23 +252,27 @@ public class SimpleTaskExplorerTests {
return taskExecution;
}
@EnableTask
protected static class TestConfiguration {
}
private void initializeJdbcExplorerTest(){
this.context = new AnnotationConfigApplicationContext();
this.context.register(TestConfiguration.class,
EmbeddedDataSourceConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
dataSource = this.context.getBean(DataSource.class);
dao = new JdbcTaskExecutionDao(dataSource);
context.getAutowireCapableBeanFactory().autowireBeanProperties(this,
AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
}
private void initializeMapExplorerTest(){
dao = new MapTaskExecutionDao();
private void initializeMapExplorerTest() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(TestConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
context.getAutowireCapableBeanFactory().autowireBeanProperties(this,
AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
}
private enum DaoType{jdbc, map}
}

View File

@@ -21,9 +21,10 @@ import javax.sql.DataSource;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.cloud.task.annotation.EnableTask;
import org.springframework.cloud.task.configuration.SimpleTaskConfiguration;
import org.springframework.cloud.task.repository.TaskExecution;
import org.springframework.cloud.task.repository.TaskRepository;
import org.springframework.cloud.task.util.TaskExecutionCreator;
@@ -47,7 +48,7 @@ public class SimpleTaskRepositoryJdbcTests {
@Before
public void setup() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(TestConfiguration.class,
this.context.register(SimpleTaskConfiguration.class,
EmbeddedDataSourceConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
@@ -92,10 +93,5 @@ public class SimpleTaskRepositoryJdbcTests {
dataSource, expectedTaskExecution.getExecutionId());
TestVerifierUtils.verifyTaskExecution(expectedTaskExecution, actualTaskExecution);
}
@EnableTask
protected static class TestConfiguration {
}
}

View File

@@ -27,12 +27,12 @@ import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.cloud.task.annotation.EnableTask;
import org.springframework.cloud.task.configuration.TaskConfigurer;
import org.springframework.cloud.task.repository.TaskRepository;
import org.springframework.cloud.task.configuration.SimpleTaskConfiguration;
import org.springframework.cloud.task.configuration.TestConfiguration;
import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
@@ -88,7 +88,7 @@ public class TaskDatabaseInitializerTests {
@Test(expected = BeanCreationException.class)
public void testMultipleDataSourcesContext() throws Exception {
this.context = new AnnotationConfigApplicationContext();
this.context.register( TestConfiguration.class,
this.context.register( SimpleTaskConfiguration.class,
EmbeddedDataSourceConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
DataSource dataSource = mock(DataSource.class);
@@ -96,19 +96,6 @@ public class TaskDatabaseInitializerTests {
this.context.refresh();
}
@EnableTask
protected static class TestConfiguration {
}
@EnableTask
protected static class TestCustomConfiguration implements TaskConfigurer {
@Override
public TaskRepository getTaskRepository() {
return new SimpleTaskRepository(new MapTaskExecutionDao());
}
}
@Configuration
protected static class EmptyConfiguration {
}
public static class EmptyConfiguration {}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2015 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.cloud.task.util;
import org.springframework.boot.CommandLineRunner;
import org.springframework.cloud.task.annotation.Task;
import org.springframework.stereotype.Component;
/**
* Basic {@link CommandLineRunner} implementation, with task annotation.
* @author Glenn Renfro
*/
@Task
@Component
public class TaskBasic implements CommandLineRunner{
@Override
public void run(String... args) {
//noop
}
}

View File

@@ -16,8 +16,7 @@
package org.springframework.cloud.task.util;
import org.aspectj.lang.JoinPoint;
import org.springframework.cloud.task.configuration.TaskHandler;
import org.springframework.cloud.task.listener.TaskLifecycleListener;
import org.springframework.cloud.task.repository.TaskRepository;
import org.springframework.cloud.task.repository.dao.MapTaskExecutionDao;
import org.springframework.cloud.task.repository.support.SimpleTaskRepository;
@@ -38,14 +37,7 @@ public class TestDefaultConfiguration {
}
@Bean
public TaskHandler taskHandler(){
return new TaskHandler();
public TaskLifecycleListener taskHandler(){
return new TaskLifecycleListener(taskRepository());
}
@Bean
public JoinPoint joinPoint(){
return new TestJoinPoint();
}
}

View File

@@ -1,64 +0,0 @@
/*
* Copyright 2015 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.cloud.task.util;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.SourceLocation;
/**
* Stubbed out join point for testing purposes.
*
* @author Glenn Renfro
*/
public class TestJoinPoint implements JoinPoint {
public String toShortString() {
return null;
}
public String toLongString() {
return null;
}
public Object getThis() {
return null;
}
public Object getTarget() {
return new TaskBasic();
}
public Object[] getArgs() {
return new Object[0];
}
public Signature getSignature() {
return null;
}
public SourceLocation getSourceLocation() {
return null;
}
public String getKind() {
return null;
}
public StaticPart getStaticPart() {
return null;
}
}