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

@@ -25,16 +25,15 @@ $ mvn -s settings.xml clean install
public class MyApp {
@Bean
public MyTask myTask() {
return new MyTask();
public MyTaskApplication myTask() {
return new MyTaskApplication();
}
public static void main(String[] args) {
SpringApplication.run(MyApp.class);
}
@Task("HelloWorldTask")
public static class MyTask implements CommandLineRunner {
public static class MyTaskApplication implements CommandLineRunner {
@Override
public void run(String... strings) throws Exception {

View File

@@ -8,7 +8,7 @@
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.3.0.RELEASE</version>
<version>1.3.2.BUILD-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>

View File

@@ -39,18 +39,10 @@
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
@@ -71,6 +63,10 @@
<artifactId>tomcat-jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-infrastructure</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,44 +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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that identifies a class as a task. This annotation will serve as the
* main “hook” to activate the various Spring Cloud Task features.
*
* @author Glenn Renfro
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Task {
/**
* Establishes the name associated with the task. The default is empty.
* @return returns name associated with the task or an empty string.
*/
public String value() default "";
}

View File

@@ -1,4 +0,0 @@
/**
* Annotations for spring cloud task.
*/
package org.springframework.cloud.task.annotation;

View File

@@ -20,6 +20,8 @@ import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.cloud.task.repository.TaskExplorer;
import org.springframework.cloud.task.repository.TaskRepository;
import org.springframework.cloud.task.repository.dao.JdbcTaskExecutionDao;
@@ -29,6 +31,8 @@ import org.springframework.cloud.task.repository.support.JdbcTaskRepositoryFacto
import org.springframework.cloud.task.repository.support.MapTaskExplorerFactoryBean;
import org.springframework.cloud.task.repository.support.MapTaskRepositoryFactoryBean;
import org.springframework.cloud.task.repository.support.SimpleTaskRepository;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Default implementation of the TaskConfigurer interface. If no {@link TaskConfigurer}
@@ -52,6 +56,8 @@ public class DefaultTaskConfigurer implements TaskConfigurer {
private TaskExplorer taskExplorer;
private PlatformTransactionManager transactionManager;
public DefaultTaskConfigurer(){
initialize();
}
@@ -61,14 +67,21 @@ public class DefaultTaskConfigurer implements TaskConfigurer {
initialize();
}
@Override
public TaskRepository getTaskRepository() {
return taskRepository;
}
@Override
public TaskExplorer getTaskExplorer() {
return taskExplorer;
}
@Override
public PlatformTransactionManager getTransactionManager() {
return this.transactionManager;
}
private void initialize(){
logger.debug("Initializing TaskRepository");
if (dataSource == null) {
@@ -78,6 +91,7 @@ public class DefaultTaskConfigurer implements TaskConfigurer {
MapTaskExplorerFactoryBean mapTaskExplorerFactoryBean =
new MapTaskExplorerFactoryBean();
taskExplorer = mapTaskExplorerFactoryBean.getObject();
transactionManager = new ResourcelessTransactionManager();
}
else {
@@ -87,6 +101,7 @@ public class DefaultTaskConfigurer implements TaskConfigurer {
JdbcTaskExplorerFactoryBean jdbcTaskExplorerFactoryBean =
new JdbcTaskExplorerFactoryBean(dataSource);
taskExplorer = jdbcTaskExplorerFactoryBean.getObject();
transactionManager = new DataSourceTransactionManager(dataSource);
}
}

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.springframework.cloud.task.annotation;
package org.springframework.cloud.task.configuration;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
@@ -24,16 +24,12 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.cloud.task.configuration.DefaultTaskConfigurer;
import org.springframework.cloud.task.configuration.SimpleTaskConfiguration;
import org.springframework.cloud.task.configuration.TaskConfigurer;
import org.springframework.cloud.task.repository.TaskRepository;
import org.springframework.context.annotation.Import;
/**
* <p>
* Enable Spring Task features and provide a base configuration for setting up
* {@link Task} objects in an &#064;Configuration class.
* Enable Spring Task features.
*
* <pre class="code">
* &#064;Configuration

View File

@@ -23,15 +23,17 @@ import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.task.listener.TaskLifecycleListener;
import org.springframework.cloud.task.repository.TaskRepository;
import org.springframework.cloud.task.repository.support.TaskDatabaseInitializer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.ResourceLoader;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
@@ -41,13 +43,12 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
*
* @author Glenn Renfro
*/
@EnableTransactionManagement
@Configuration
@EnableTransactionManagement
public class SimpleTaskConfiguration {
protected static final Log logger = LogFactory.getLog(SimpleTaskConfiguration.class);
@Autowired
private ApplicationContext context;
@@ -66,17 +67,23 @@ public class SimpleTaskConfiguration {
private TaskConfigurer configurer;
@Bean
@Scope("prototype")
public TaskHandler taskHandler() {
return new TaskHandler();
}
private PlatformTransactionManager transactionManager;
@Bean
public TaskRepository taskRepository(){
return taskRepository;
}
@Bean
public TaskLifecycleListener taskLifecycleListener() {
return new TaskLifecycleListener(taskRepository());
}
@Bean
public PlatformTransactionManager transactionManager() {
return this.transactionManager;
}
/**
* Sets up the basic components by extracting them from the {@link TaskConfigurer}, defaulting to some
* sensible values as long as a unique DataSource is available.
@@ -93,6 +100,7 @@ public class SimpleTaskConfiguration {
logger.debug(String.format("Using %s TaskConfigurer",
configurer.getClass().getName()));
taskRepository = configurer.getTaskRepository();
transactionManager = configurer.getTransactionManager();
initialized = true;
}

View File

@@ -16,7 +16,9 @@
package org.springframework.cloud.task.configuration;
import org.springframework.cloud.task.repository.TaskExplorer;
import org.springframework.cloud.task.repository.TaskRepository;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Provides a strategy interface for providing configuration
@@ -33,4 +35,7 @@ public interface TaskConfigurer {
*/
public TaskRepository getTaskRepository();
public PlatformTransactionManager getTransactionManager();
public TaskExplorer getTaskExplorer();
}

View File

@@ -1,124 +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.configuration;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.cloud.task.annotation.Task;
import org.springframework.cloud.task.repository.TaskExecution;
import org.springframework.cloud.task.repository.TaskRepository;
import org.springframework.context.ApplicationContext;
/**
* Offers the advice on how to record tasks to the repository for both
* {@link org.springframework.boot.ApplicationRunner} and
* {@link org.springframework.boot.CommandLineRunner} spring boot applications.
*
* @author Glenn Renfro
*/
@Aspect
public class TaskHandler {
@Autowired
private ApplicationContext context;
@Autowired
private TaskRepository repository;
private String taskName;
private String executionId;
private TaskExecution taskExecution;
/**
* Looks for any {@link CommandLineRunner}.run method with its class annotated with @Task
* and calls the repository implementation to store the start of the task in the repo
* before the run starts.
*
* @param joinPoint the point where the run method in a {@link CommandLineRunner} or
* {@link ApplicationRunner} is executed
*/
@Before("within( @org.springframework.cloud.task.annotation.Task *) && (execution(* org.springframework.boot.CommandLineRunner.run(..)) || execution(* org.springframework.boot.ApplicationRunner.run(..)))")
public void beforeCommandLineRunner(JoinPoint joinPoint) {
executionId = UUID.randomUUID().toString();
taskExecution = new TaskExecution();
Task a = joinPoint.getTarget().getClass().getAnnotation(Task.class);
taskName = a.value();
if (taskName == null || taskName.length() == 0) {
taskName = joinPoint.getTarget().getClass().getName();
}
taskExecution.setTaskName(taskName);
taskExecution.setStartTime(new Date());
taskExecution.setExecutionId(executionId);
repository.createTaskExecution(taskExecution);
}
/**
* Looks for any {@link CommandLineRunner}.run method with its class annotated with @Task
* and calls repository implementation to store the exit of the task in the repo after
* run returns result.
*
* @param joinPoint the point where the run method in a {@link CommandLineRunner} or
* {@link ApplicationRunner} is executed
*/
@AfterReturning("within( @org.springframework.cloud.task.annotation.Task *) && (execution(* org.springframework.boot.CommandLineRunner.run(..)) || execution(* org.springframework.boot.ApplicationRunner.run(..)))")
public void afterReturnCommandLineRunner(JoinPoint joinPoint) {
int result = 0;
List<ExitCodeGenerator> generators = new ArrayList<ExitCodeGenerator>();
generators
.addAll(context.getBeansOfType(ExitCodeGenerator.class).values());
for (ExitCodeGenerator generator : generators) {
result = generator.getExitCode();
}
taskExecution.setEndTime(new Date());
taskExecution.setExitCode(result);
repository.update(taskExecution);
}
/**
* Looks for any {@link CommandLineRunner}.run method with its class annotated with @Task
* and calls the repository implementation to store the exitCode of 1
* for the task in the repo in the case of an exception.
*
* @param joinPoint the point where the run method in a {@link CommandLineRunner} or
* {@link ApplicationRunner} is executed
*/
@AfterThrowing("within( @org.springframework.cloud.task.annotation.Task *) && (execution(* org.springframework.boot.CommandLineRunner.run(..)) || execution(* org.springframework.boot.ApplicationRunner.run(..)))")
public void logExceptionCommandLineRunner(JoinPoint joinPoint) {
taskExecution.setEndTime(new Date());
taskExecution.setExitCode(1);
repository.update(taskExecution);
}
public TaskExecution getTaskExecution() {
return taskExecution;
}
}

View File

@@ -0,0 +1,170 @@
/*
* 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 java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.cloud.task.repository.TaskExecution;
import org.springframework.cloud.task.repository.TaskRepository;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
/**
* Monitors the lifecycle of a task. This listener will record both the start and end of
* a task in the registered {@link TaskRepository}.
*
* The following events are used to identify the start and end of a task:
*
* <ul>
* <li>{@link ContextRefreshedEvent} - Used to identify the start of a task. A task
* is expected to contain a single application context.</li>
* <li>{@link ContextClosedEvent} - Used to identify the successful end of a task.</li>
* <li>{@link ApplicationFailedEvent} - Used to identify the failure of a task.</li>
* </ul>
*
* <b>NOTE:</b> Multiple contexts (including parent/child relationships) will result in
* only the first context refresh that contains this instance being recorded.
*
* @author Michael Minella
*/
public class TaskLifecycleListener implements ApplicationListener<ApplicationEvent>,
ApplicationContextAware {
private final static Logger logger = LoggerFactory.getLogger(TaskLifecycleListener.class);
private final TaskRepository taskRepository;
private TaskExecution taskExecution;
private boolean started = false;
private ApplicationContext applicationContext;
/**
* @param taskRepository The repository to record executions in.
*/
public TaskLifecycleListener(TaskRepository taskRepository) {
this.taskRepository = taskRepository;
}
/**
* Utilizes {@link ApplicationEvent}s to determine the start, end, and failure of a
* task. Specifically:
* <ul>
* <li>{@link ContextRefreshedEvent} - Start of a task</li>
* <li>{@link ContextClosedEvent} - Successful end of a task</li>
* <li>{@link ApplicationFailedEvent} - Failure of a task</li>
* </ul>
*
* @param applicationEvent The application being listened for.
*/
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if(applicationEvent instanceof ContextRefreshedEvent) {
doTaskStart();
started = true;
}
else if(applicationEvent instanceof ContextClosedEvent) {
doTaskEnd();
}
else if(applicationEvent instanceof ApplicationFailedEvent) {
doTaskFailed(((ApplicationFailedEvent) applicationEvent).getException());
}
}
private void doTaskFailed(Throwable exception) {
if(started) {
this.taskExecution.setEndTime(new Date());
//TODO: get exit code from new event thrown by Boot.
this.taskExecution.setExitCode(1);
this.taskExecution.setExitMessage(stackTraceToString(exception));
taskRepository.update(taskExecution);
}
else {
logger.error("An event to fail a task has been received for a task that has " +
"not yet started.");
}
}
private String stackTraceToString(Throwable exception) {
StringWriter writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
exception.printStackTrace(printWriter);
return writer.toString();
}
private void doTaskEnd() {
if(started) {
this.taskExecution.setEndTime(new Date());
//TODO: get exit code from new event thrown by Boot.
this.taskExecution.setExitCode(0);
taskRepository.update(taskExecution);
}
else {
logger.error("An event to end a task has been received for a task that has " +
"not yet started.");
}
}
private void doTaskStart() {
if(!started) {
String executionId = UUID.randomUUID().toString();
this.taskExecution = new TaskExecution();
this.taskExecution.setTaskName(applicationContext.getId());
this.taskExecution.setStartTime(new Date());
this.taskExecution.setExecutionId(executionId);
this.taskRepository.createTaskExecution(this.taskExecution);
}
else {
logger.error("Multiple start events have been received. The first one was " +
"recorded.");
}
}
/**
* Used for testing purposes
* @return the current {@link TaskExecution}
*/
TaskExecution getTaskExecution() {
return this.taskExecution;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
}

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;
}
}

View File

@@ -22,13 +22,13 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.task.annotation.EnableTask;
import org.springframework.cloud.task.annotation.Task;
import org.springframework.cloud.task.configuration.EnableTask;
import org.springframework.context.annotation.Bean;
/**
@@ -49,9 +49,8 @@ public class TaskApplication {
}
/**
* A commandline runner that prints a timestamp and is annotated with @Task.
* A commandline runner that prints a timestamp.
*/
@Task("Demo Timestamp Task")
public class TimestampTask implements CommandLineRunner {
private final org.slf4j.Logger logger = LoggerFactory.getLogger(TimestampTask.class);

View File

@@ -0,0 +1 @@
spring.application.name=Demo Timestamp Task