From 20a56c9581349d377e0a22acb0f8c0f2a86c24a7 Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Tue, 23 Feb 2016 10:44:54 -0500 Subject: [PATCH] SCT-35 Tasks Support TaskExecutionListener resolves spring-cloud/spring-cloud-task#35 TaskExecutionListener now supports TaskExecution Params --- .../task/listener/TaskExecutionListener.java | 50 +++++++ .../task/listener/TaskLifecycleListener.java | 54 +++++++- .../listener/TaskExecutionListenerTests.java | 123 ++++++++++++++++++ .../listener/TaskLifecycleListenerTests.java | 1 - .../TestDefaultListenerConfiguration.java | 93 +++++++++++++ .../src/main/asciidoc/features.adoc | 12 ++ 6 files changed, 331 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionListener.java create mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/listener/TaskExecutionListenerTests.java create mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultListenerConfiguration.java diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionListener.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionListener.java new file mode 100644 index 00000000..014accdd --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionListener.java @@ -0,0 +1,50 @@ +/* + * 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. + * 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 org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.cloud.task.repository.TaskRepository; + +/** + * The listener interface for receiving task execution events. + * @author Glenn Renfro + */ +public interface TaskExecutionListener { + + /** + * Invoked after the {@link TaskExecution} has been stored in the {@link TaskRepository}. + * @param taskExecution instance containing the information about the current task. + */ + public void onTaskStartup(TaskExecution taskExecution); + + /** + * Invoked before the {@link TaskExecution} has been updated in the {@link TaskRepository} + * upon task end. + * @param taskExecution instance containing the information about the current task. + */ + public void onTaskEnd(TaskExecution taskExecution); + + /** + * Invoked if an uncaught exception occurs during a task execution. This invocation + * will occur before the {@link TaskExecution} has been updated in the {@link TaskRepository} + * and before the onTaskEnd is called. + * @param taskExecution instance containing the information about the current task. + * @param throwable the uncaught exception that was thrown during task execution. + */ + public void onTaskFailed(TaskExecution taskExecution, Throwable throwable); + +} 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 b937008d..5fd6d162 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 @@ -19,12 +19,14 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ExitCodeEvent; import org.springframework.boot.context.event.ApplicationFailedEvent; @@ -57,6 +59,9 @@ import org.springframework.util.Assert; */ public class TaskLifecycleListener implements ApplicationListener{ + @Autowired(required = false) + Collection taskExecutionListeners; + private final static Logger logger = LoggerFactory.getLogger(TaskLifecycleListener.class); private final TaskRepository taskRepository; @@ -142,6 +147,11 @@ public class TaskLifecycleListener implements ApplicationListener()); + verifyListenerResults(true, false, false, taskExecution,taskExecutionListener); + } + + @Test + public void testTaskUpdate() { + context.refresh(); + TestDefaultListenerConfiguration.TestTaskExecutionListener taskExecutionListener = + context.getBean(TestDefaultListenerConfiguration.TestTaskExecutionListener.class); + context.publishEvent(new ContextClosedEvent(context)); + + TaskExecution taskExecution = new TaskExecution(0, 0, "wombat", + new Date(), new Date(), null, new ArrayList()); + verifyListenerResults(true, true, false, taskExecution,taskExecutionListener); + } + + @Test + public void testTaskFail() { + RuntimeException exception = new RuntimeException(EXCEPTION_MESSAGE); + context.refresh(); + context.publishEvent(new ApplicationFailedEvent(new SpringApplication(), new String[0], context, exception)); + context.publishEvent(new ContextClosedEvent(context)); + TestDefaultListenerConfiguration.TestTaskExecutionListener taskExecutionListener = + context.getBean(TestDefaultListenerConfiguration.TestTaskExecutionListener.class); + + TaskExecution taskExecution = new TaskExecution(0, 1, "wombat", new Date(), + new Date(), null, new ArrayList()); + verifyListenerResults(true, true, true, taskExecution,taskExecutionListener); + } + + private void verifyListenerResults (boolean isTaskStartup, boolean isTaskEnd, + boolean isTaskFailed, TaskExecution taskExecution, + TestDefaultListenerConfiguration.TestTaskExecutionListener actualListener){ + assertEquals(isTaskStartup,actualListener.isTaskStartup()); + assertEquals(isTaskEnd,actualListener.isTaskEnd()); + assertEquals(isTaskFailed,actualListener.isTaskFailed()); + if(isTaskFailed){ + assertEquals(TestDefaultListenerConfiguration.TestTaskExecutionListener.END_MESSAGE, actualListener.getTaskExecution().getExitMessage()); + assertNotNull(actualListener.getThrowable()); + assertTrue(actualListener.getThrowable() instanceof RuntimeException); + } + else if(isTaskEnd){ + assertEquals(TestDefaultListenerConfiguration.TestTaskExecutionListener.END_MESSAGE, actualListener.getTaskExecution().getExitMessage()); + assertNull(actualListener.getThrowable()); + } + else { + assertEquals(TestDefaultListenerConfiguration.TestTaskExecutionListener.START_MESSAGE, actualListener.getTaskExecution().getExitMessage()); + assertNull(actualListener.getThrowable()); + } + + assertEquals(taskExecution.getExecutionId(), actualListener.getTaskExecution().getExecutionId()); + assertEquals(taskExecution.getExitCode(), actualListener.getTaskExecution().getExitCode()); + } +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/listener/TaskLifecycleListenerTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/listener/TaskLifecycleListenerTests.java index 41b1bccd..4f20f63e 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/listener/TaskLifecycleListenerTests.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/listener/TaskLifecycleListenerTests.java @@ -30,7 +30,6 @@ import java.util.Set; import org.junit.After; import org.junit.Before; import org.junit.Test; - import org.springframework.boot.ApplicationArguments; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultListenerConfiguration.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultListenerConfiguration.java new file mode 100644 index 00000000..bb7e9ba5 --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultListenerConfiguration.java @@ -0,0 +1,93 @@ +/* + * 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. + * 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.cloud.task.listener.TaskExecutionListener; +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Initializes the beans needed to TestExecutionListenerTests. + * + * @author Glenn Renfro + */ +@Configuration +public class TestDefaultListenerConfiguration { + + @Bean + public TestTaskExecutionListener taskExecutionListener() { + return new TestTaskExecutionListener(); + } + + + public class TestTaskExecutionListener implements TaskExecutionListener { + + public static final String START_MESSAGE = "FOO"; + public static final String ERROR_MESSAGE = "BAR"; + public static final String END_MESSAGE = "BAZ"; + + private boolean isTaskStartup; + private boolean isTaskEnd; + private boolean isTaskFailed; + private TaskExecution taskExecution; + private Throwable throwable; + + @Override + public void onTaskStartup(TaskExecution taskExecution) { + isTaskStartup = true; + this.taskExecution = taskExecution; + this.taskExecution.setExitMessage(START_MESSAGE); + } + + @Override + public void onTaskEnd(TaskExecution taskExecution) { + isTaskEnd = true; + this.taskExecution = taskExecution; + this.taskExecution.setExitMessage(END_MESSAGE); + } + + @Override + public void onTaskFailed(TaskExecution taskExecution, Throwable throwable) { + isTaskFailed = true; + this.taskExecution = taskExecution; + this.throwable = throwable; + this.taskExecution.setExitMessage(ERROR_MESSAGE); + } + + public boolean isTaskStartup() { + return isTaskStartup; + } + + public boolean isTaskEnd() { + return isTaskEnd; + } + + public boolean isTaskFailed() { + return isTaskFailed; + } + + public TaskExecution getTaskExecution() { + return this.taskExecution; + } + + public Throwable getThrowable(){ + return throwable; + } + + } +} diff --git a/spring-cloud-task-docs/src/main/asciidoc/features.adoc b/spring-cloud-task-docs/src/main/asciidoc/features.adoc index f2b35941..1a8b8a2b 100644 --- a/spring-cloud-task-docs/src/main/asciidoc/features.adoc +++ b/spring-cloud-task-docs/src/main/asciidoc/features.adoc @@ -172,4 +172,16 @@ following options (in order of precedence): . The application name as resolved using Spring Boot's rules (obtained via `ApplicationContext#getId`). +[[features-task-execution-listener]] +=== Task Execution Listener +Allows a user to register listeners for specific events that occur during the task +lifecycle. This is done by creating a class that implements the TaskExecutionListener +interface. The class that implements the TaskExecutionListener interface will be +notified for the following events: + +. `onTaskStartup` - prior to the storing the TaskExecution into the TaskRepository +. `onTaskEnd` - prior to the updating of the TaskExecution entry in the TaskRepository +marking the final state of the task. +. `onTaskFailed` - prior to the `onTaskEnd` method being invoked when an unhandled + exception is thrown by the task.