SCT-95 Support TaskExecutionListener Annotations
Added the ability to implement TaskExecutionListener functionality without implementing the TaskExecutionListener interface. resolves spring-cloud/spring-cloud-task#95
This commit is contained in:
committed by
Michael Minella
parent
20a56c9581
commit
f486050086
@@ -27,6 +27,8 @@ 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.repository.TaskExplorer;
|
||||
import org.springframework.cloud.task.repository.TaskNameResolver;
|
||||
import org.springframework.cloud.task.repository.TaskRepository;
|
||||
@@ -72,6 +74,14 @@ public class SimpleTaskConfiguration {
|
||||
return new TaskLifecycleListener(taskRepository(), taskNameResolver(), this.applicationArguments);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskListenerExecutor taskListenerExecutor(ConfigurableApplicationContext context) throws Exception
|
||||
{
|
||||
TaskListenerExecutorFactory taskListenerExecutorFactory =
|
||||
new TaskListenerExecutorFactory(context);
|
||||
return taskListenerExecutorFactory.getObject();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager transactionManager() {
|
||||
return this.configurer.getTransactionManager();
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Base Exception for any Task issues.
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public class TaskException extends RuntimeException {
|
||||
|
||||
public TaskException(String message, Throwable e){
|
||||
super(message, e);
|
||||
}
|
||||
|
||||
public TaskException(String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Is thrown when executing a task.
|
||||
*
|
||||
* @author Glenn Renfro.
|
||||
*/
|
||||
public class TaskExecutionException extends TaskException {
|
||||
|
||||
public TaskExecutionException(String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TaskExecutionException(String message, Throwable throwable){
|
||||
super(message, throwable);
|
||||
}
|
||||
}
|
||||
@@ -46,5 +46,4 @@ public interface TaskExecutionListener {
|
||||
* @param throwable the uncaught exception that was thrown during task execution.
|
||||
*/
|
||||
public void onTaskFailed(TaskExecution taskExecution, Throwable throwable);
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ 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;
|
||||
@@ -60,7 +61,7 @@ import org.springframework.util.Assert;
|
||||
public class TaskLifecycleListener implements ApplicationListener<ApplicationEvent>{
|
||||
|
||||
@Autowired(required = false)
|
||||
Collection<TaskExecutionListener> taskExecutionListeners;
|
||||
private Collection<TaskExecutionListener> taskExecutionListeners;
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(TaskLifecycleListener.class);
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.cloud.task.listener.TaskExecutionListener;
|
||||
import org.springframework.cloud.task.repository.TaskExecution;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* {@link TaskExecutionListener#onTaskEnd(TaskExecution)}
|
||||
* </p>
|
||||
*
|
||||
* <pre class="code">
|
||||
* public class MyListener {
|
||||
|
||||
* @AfterTask
|
||||
* public void doSomething(TaskExecution taskExecution) {
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface AfterTask {
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.cloud.task.listener.TaskExecutionListener;
|
||||
import org.springframework.cloud.task.repository.TaskExecution;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* {@link TaskExecutionListener#onTaskStartup(TaskExecution)}
|
||||
* </p>
|
||||
*
|
||||
* <pre class="code">
|
||||
* public class MyListener {
|
||||
|
||||
* @BeforeTask
|
||||
* public void doSomething(TaskExecution taskExecution) {
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface BeforeTask {
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.cloud.task.listener.TaskExecutionListener;
|
||||
import org.springframework.cloud.task.repository.TaskExecution;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* {@link TaskExecutionListener#onTaskFailed(TaskExecution, Throwable)}
|
||||
* </p>
|
||||
*
|
||||
* <pre class="code">
|
||||
* public class MyListener {
|
||||
|
||||
* @FailedTask
|
||||
* public void doSomething(TaskExecution taskExecution, Throwable throwable) {
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface FailedTask {
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.cloud.task.listener.TaskExecutionException;
|
||||
import org.springframework.cloud.task.listener.TaskExecutionListener;
|
||||
import org.springframework.cloud.task.repository.TaskExecution;
|
||||
|
||||
/**
|
||||
* Identifies all beans that contain a TaskExecutionListener annotation and stores the
|
||||
* associated method so that it can be called by the {@link TaskExecutionListener} at the
|
||||
* appropriate time.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public class TaskListenerExecutor implements TaskExecutionListener{
|
||||
|
||||
private Map<Method, Object> beforeTaskInstances;
|
||||
|
||||
private Map<Method, Object> afterTaskInstances;
|
||||
|
||||
private Map<Method, Object> failedTaskInstances;
|
||||
|
||||
public TaskListenerExecutor(Map<Method, Object> beforeTaskInstances,
|
||||
Map<Method, Object> afterTaskInstances,
|
||||
Map<Method, Object> failedTaskInstances){
|
||||
|
||||
this.beforeTaskInstances = beforeTaskInstances;
|
||||
this.afterTaskInstances = afterTaskInstances;
|
||||
this.failedTaskInstances = failedTaskInstances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all the methods that have been annotated with @BeforeTask.
|
||||
* @param taskExecution associated with the event.
|
||||
*/
|
||||
@Override
|
||||
public void onTaskStartup(TaskExecution taskExecution) {
|
||||
executeTaskListener(taskExecution, beforeTaskInstances.keySet(), beforeTaskInstances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all the methods that have been annotated with @AfterTask.
|
||||
* @param taskExecution associated with the event.
|
||||
*/
|
||||
@Override
|
||||
public void onTaskEnd(TaskExecution taskExecution) {
|
||||
executeTaskListener(taskExecution, afterTaskInstances.keySet(), afterTaskInstances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all the methods that have been annotated with @FailedTask.
|
||||
* @param throwable that was not caught for the task execution.
|
||||
* @param taskExecution associated with the event.
|
||||
*/
|
||||
@Override
|
||||
public void onTaskFailed(TaskExecution taskExecution, Throwable throwable) {
|
||||
executeTaskListenerWithThrowable(taskExecution, throwable,
|
||||
failedTaskInstances.keySet(),failedTaskInstances);
|
||||
}
|
||||
|
||||
private void executeTaskListener(TaskExecution taskExecution, Set<Method> methods, Map<Method, Object> instances){
|
||||
for (Method method : methods) {
|
||||
try {
|
||||
method.invoke(instances.get(method),taskExecution);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
throw new TaskExecutionException("@BeforeTask and @AfterTask annotated methods must be public.", e);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new TaskExecutionException("Failed to process @BeforeTask or @AfterTask" +
|
||||
"annotation because: ", e);
|
||||
}
|
||||
catch (IllegalArgumentException e){
|
||||
throw new TaskExecutionException("taskExecution parameter is required for @BeforeTask and @AfterTask annotated methods", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void executeTaskListenerWithThrowable(TaskExecution taskExecution,
|
||||
Throwable throwable, Set<Method> methods, Map<Method, Object> instances){
|
||||
for (Method method : methods) {
|
||||
try {
|
||||
method.invoke(instances.get(method),taskExecution, throwable);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
throw new TaskExecutionException("@FailedTask annotated methods must be public.", e);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new TaskExecutionException("Failed to process @FailedTask " +
|
||||
"annotation because: ", e);
|
||||
}
|
||||
catch (IllegalArgumentException e){
|
||||
throw new TaskExecutionException("taskExecution and throwable parameters "
|
||||
+ "are required for @FailedTask annotated methods", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
|
||||
import org.springframework.aop.scope.ScopedObject;
|
||||
import org.springframework.aop.scope.ScopedProxyUtils;
|
||||
import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.MethodIntrospector;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
|
||||
/**
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public class TaskListenerExecutorFactory implements FactoryBean<TaskListenerExecutor> {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(TaskListenerExecutor.class);
|
||||
|
||||
private final Set<Class<?>> nonAnnotatedClasses =
|
||||
Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>());
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
private Map<Method, Object> beforeTaskInstances;
|
||||
|
||||
private Map<Method, Object> afterTaskInstances;
|
||||
|
||||
private Map<Method, Object> failedTaskInstances;
|
||||
|
||||
public TaskListenerExecutorFactory(ConfigurableApplicationContext context){
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskListenerExecutor getObject() throws Exception {
|
||||
beforeTaskInstances = new HashMap<>();
|
||||
afterTaskInstances = new HashMap<>();
|
||||
failedTaskInstances = new HashMap<>();
|
||||
initializeExecutor();
|
||||
return new TaskListenerExecutor(beforeTaskInstances, afterTaskInstances, failedTaskInstances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return TaskListenerExecutor.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initializeExecutor( ) {
|
||||
ConfigurableListableBeanFactory factory = context.getBeanFactory();
|
||||
for( String beanName : context.getBeanDefinitionNames()) {
|
||||
|
||||
if (!ScopedProxyUtils.isScopedTarget(beanName)) {
|
||||
Class<?> type = null;
|
||||
try {
|
||||
type = AutoProxyUtils.determineTargetClass(factory, beanName);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
|
||||
}
|
||||
}
|
||||
if (type != null) {
|
||||
if (ScopedObject.class.isAssignableFrom(type)) {
|
||||
try {
|
||||
type = AutoProxyUtils.determineTargetClass(factory,
|
||||
ScopedProxyUtils.getTargetBeanName(beanName));
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
// An invalid scoped proxy arrangement - let's ignore it.
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Could not resolve target bean for scoped proxy '" + beanName + "'", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
processBean(beanName, type);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
throw new BeanInitializationException("Failed to process @BeforeTask " +
|
||||
"annotation on bean with name '" + beanName + "'", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void processBean(String beanName, final Class<?> type){
|
||||
if (!this.nonAnnotatedClasses.contains(type)) {
|
||||
Map<Method, BeforeTask> beforeTaskMethods =
|
||||
(new MethodGetter<BeforeTask>()).getMethods(type, BeforeTask.class);
|
||||
Map<Method, AfterTask> afterTaskMethods =
|
||||
(new MethodGetter<AfterTask>()).getMethods(type, AfterTask.class);
|
||||
Map<Method, FailedTask> failedTaskMethods =
|
||||
(new MethodGetter<FailedTask>()).getMethods(type, FailedTask.class);
|
||||
|
||||
if (beforeTaskMethods.isEmpty() && afterTaskMethods.isEmpty()) {
|
||||
this.nonAnnotatedClasses.add(type);
|
||||
return;
|
||||
}
|
||||
if(!beforeTaskMethods.isEmpty()) {
|
||||
for(Method beforeTaskMethod : beforeTaskMethods.keySet()) {
|
||||
this.beforeTaskInstances.put(beforeTaskMethod, context.getBean(beanName));
|
||||
}
|
||||
}
|
||||
if(!afterTaskMethods.isEmpty()){
|
||||
for(Method afterTaskMethod : afterTaskMethods.keySet()) {
|
||||
this.afterTaskInstances.put(afterTaskMethod, context.getBean(beanName));
|
||||
}
|
||||
}
|
||||
if(!failedTaskMethods.isEmpty()){
|
||||
for(Method failedTaskMethod : failedTaskMethods.keySet()) {
|
||||
this.failedTaskInstances.put(failedTaskMethod, context.getBean(beanName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class MethodGetter<T extends Annotation> {
|
||||
public Map<Method, T> getMethods(final Class<?> type, final Class<T> annotationClass){
|
||||
return MethodIntrospector.selectMethods(type,
|
||||
new MethodIntrospector.MetadataLookup<T>() {
|
||||
@Override
|
||||
public T inspect(Method method) {
|
||||
return AnnotationUtils.findAnnotation(method, annotationClass);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,15 +25,22 @@ import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
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.repository.TaskExecution;
|
||||
import org.springframework.cloud.task.util.TestDefaultConfiguration;
|
||||
import org.springframework.cloud.task.util.TestDefaultListenerConfiguration;
|
||||
import org.springframework.cloud.task.util.TestListener;
|
||||
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.event.ContextClosedEvent;
|
||||
|
||||
/**
|
||||
@@ -47,35 +54,36 @@ public class TaskExecutionListenerTests {
|
||||
|
||||
private static final String EXCEPTION_MESSAGE = "This was expected";
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
context = new AnnotationConfigApplicationContext();
|
||||
context.setId("testTask");
|
||||
context.register(TestDefaultListenerConfiguration.class,
|
||||
TestDefaultConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
context.close();
|
||||
if(context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that if a TaskExecutionListener Bean is present that the onTaskStartup method
|
||||
* is called.
|
||||
*/
|
||||
@Test
|
||||
public void testTaskCreate() {
|
||||
context.refresh();
|
||||
TestDefaultListenerConfiguration.TestTaskExecutionListener taskExecutionListener =
|
||||
context.getBean(TestDefaultListenerConfiguration.TestTaskExecutionListener.class);
|
||||
setupContextForTaskExecutionListener();
|
||||
DefaultTaskListenerConfiguration.TestTaskExecutionListener taskExecutionListener =
|
||||
context.getBean(DefaultTaskListenerConfiguration.TestTaskExecutionListener.class);
|
||||
TaskExecution taskExecution = new TaskExecution(0, null, "wombat",
|
||||
new Date(), new Date(), null, new ArrayList<String>());
|
||||
verifyListenerResults(true, false, false, taskExecution,taskExecutionListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that if a TaskExecutionListener Bean is present that the onTaskEnd method
|
||||
* is called.
|
||||
*/
|
||||
@Test
|
||||
public void testTaskUpdate() {
|
||||
context.refresh();
|
||||
TestDefaultListenerConfiguration.TestTaskExecutionListener taskExecutionListener =
|
||||
context.getBean(TestDefaultListenerConfiguration.TestTaskExecutionListener.class);
|
||||
setupContextForTaskExecutionListener();
|
||||
DefaultTaskListenerConfiguration.TestTaskExecutionListener taskExecutionListener =
|
||||
context.getBean(DefaultTaskListenerConfiguration.TestTaskExecutionListener.class);
|
||||
context.publishEvent(new ContextClosedEvent(context));
|
||||
|
||||
TaskExecution taskExecution = new TaskExecution(0, 0, "wombat",
|
||||
@@ -83,41 +91,184 @@ public class TaskExecutionListenerTests {
|
||||
verifyListenerResults(true, true, false, taskExecution,taskExecutionListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that if a TaskExecutionListener Bean is present that the onTaskFailed method
|
||||
* is called.
|
||||
*/
|
||||
@Test
|
||||
public void testTaskFail() {
|
||||
RuntimeException exception = new RuntimeException(EXCEPTION_MESSAGE);
|
||||
context.refresh();
|
||||
setupContextForTaskExecutionListener();
|
||||
context.publishEvent(new ApplicationFailedEvent(new SpringApplication(), new String[0], context, exception));
|
||||
context.publishEvent(new ContextClosedEvent(context));
|
||||
TestDefaultListenerConfiguration.TestTaskExecutionListener taskExecutionListener =
|
||||
context.getBean(TestDefaultListenerConfiguration.TestTaskExecutionListener.class);
|
||||
DefaultTaskListenerConfiguration.TestTaskExecutionListener taskExecutionListener =
|
||||
context.getBean(DefaultTaskListenerConfiguration.TestTaskExecutionListener.class);
|
||||
|
||||
TaskExecution taskExecution = new TaskExecution(0, 1, "wombat", new Date(),
|
||||
new Date(), null, new ArrayList<String>());
|
||||
verifyListenerResults(true, true, true, taskExecution,taskExecutionListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that if a bean has a @BeforeTask annotation present that the associated
|
||||
* method is called.
|
||||
*/
|
||||
@Test
|
||||
public void testAnnotationCreate() throws Exception {
|
||||
setupContextForAnnotatedListener();
|
||||
DefaultAnnotationConfiguration.AnnotatedTaskListener annotatedListener =
|
||||
context.getBean(DefaultAnnotationConfiguration.AnnotatedTaskListener.class);
|
||||
TaskExecution taskExecution = new TaskExecution(0, null, "wombat",
|
||||
new Date(), new Date(), null, new ArrayList<String>());
|
||||
verifyListenerResults(true, false, false, taskExecution,annotatedListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that if a bean has a @AfterTask annotation present that the associated
|
||||
* method is called.
|
||||
*/
|
||||
@Test
|
||||
public void testAnnotationUpdate() {
|
||||
setupContextForAnnotatedListener();
|
||||
DefaultAnnotationConfiguration.AnnotatedTaskListener annotatedListener =
|
||||
context.getBean(DefaultAnnotationConfiguration.AnnotatedTaskListener.class);
|
||||
context.publishEvent(new ContextClosedEvent(context));
|
||||
|
||||
TaskExecution taskExecution = new TaskExecution(0, 0, "wombat",
|
||||
new Date(), new Date(), null, new ArrayList<String>());
|
||||
verifyListenerResults(true, true, false, taskExecution,annotatedListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that if a bean has a @FailedTask annotation present that the associated
|
||||
* method is called.
|
||||
*/
|
||||
@Test
|
||||
public void testAnnotationFail() {
|
||||
RuntimeException exception = new RuntimeException(EXCEPTION_MESSAGE);
|
||||
setupContextForAnnotatedListener();
|
||||
context.publishEvent(new ApplicationFailedEvent(new SpringApplication(), new String[0], context, exception));
|
||||
context.publishEvent(new ContextClosedEvent(context));
|
||||
DefaultAnnotationConfiguration.AnnotatedTaskListener annotatedListener =
|
||||
context.getBean(DefaultAnnotationConfiguration.AnnotatedTaskListener.class);
|
||||
|
||||
TaskExecution taskExecution = new TaskExecution(0, 1, "wombat", new Date(),
|
||||
new Date(), null, new ArrayList<String>());
|
||||
verifyListenerResults(true, true, true, taskExecution,annotatedListener);
|
||||
}
|
||||
|
||||
private void verifyListenerResults (boolean isTaskStartup, boolean isTaskEnd,
|
||||
boolean isTaskFailed, TaskExecution taskExecution,
|
||||
TestDefaultListenerConfiguration.TestTaskExecutionListener actualListener){
|
||||
TestListener actualListener){
|
||||
assertEquals(isTaskStartup,actualListener.isTaskStartup());
|
||||
assertEquals(isTaskEnd,actualListener.isTaskEnd());
|
||||
assertEquals(isTaskFailed,actualListener.isTaskFailed());
|
||||
if(isTaskFailed){
|
||||
assertEquals(TestDefaultListenerConfiguration.TestTaskExecutionListener.END_MESSAGE, actualListener.getTaskExecution().getExitMessage());
|
||||
assertEquals(TestListener.END_MESSAGE, actualListener.getTaskExecution().getExitMessage());
|
||||
assertNotNull(actualListener.getThrowable());
|
||||
assertTrue(actualListener.getThrowable() instanceof RuntimeException);
|
||||
}
|
||||
else if(isTaskEnd){
|
||||
assertEquals(TestDefaultListenerConfiguration.TestTaskExecutionListener.END_MESSAGE, actualListener.getTaskExecution().getExitMessage());
|
||||
assertEquals(TestListener.END_MESSAGE, actualListener.getTaskExecution().getExitMessage());
|
||||
assertNull(actualListener.getThrowable());
|
||||
}
|
||||
else {
|
||||
assertEquals(TestDefaultListenerConfiguration.TestTaskExecutionListener.START_MESSAGE, actualListener.getTaskExecution().getExitMessage());
|
||||
assertEquals(TestListener.START_MESSAGE, actualListener.getTaskExecution().getExitMessage());
|
||||
assertNull(actualListener.getThrowable());
|
||||
}
|
||||
|
||||
assertEquals(taskExecution.getExecutionId(), actualListener.getTaskExecution().getExecutionId());
|
||||
assertEquals(taskExecution.getExitCode(), actualListener.getTaskExecution().getExitCode());
|
||||
}
|
||||
|
||||
private void setupContextForTaskExecutionListener(){
|
||||
context = new AnnotationConfigApplicationContext(DefaultTaskListenerConfiguration.class,
|
||||
TestDefaultConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class);
|
||||
context.setId("testTask");
|
||||
}
|
||||
|
||||
private void setupContextForAnnotatedListener(){
|
||||
context = new AnnotationConfigApplicationContext(TestDefaultConfiguration.class, DefaultAnnotationConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class);
|
||||
context.setId("annotatedTask");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class DefaultAnnotationConfiguration {
|
||||
|
||||
@Bean
|
||||
public AnnotatedTaskListener annotatedTaskListener() {
|
||||
return new AnnotatedTaskListener();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskListenerExecutor taskListenerExecutor(ConfigurableApplicationContext context) throws Exception
|
||||
{
|
||||
TaskListenerExecutorFactory taskListenerExecutorFactory = new TaskListenerExecutorFactory(context);
|
||||
return taskListenerExecutorFactory.getObject();
|
||||
}
|
||||
|
||||
public static class AnnotatedTaskListener extends TestListener {
|
||||
|
||||
@BeforeTask
|
||||
public void methodA(TaskExecution taskExecution) {
|
||||
isTaskStartup = true;
|
||||
this.taskExecution = taskExecution;
|
||||
this.taskExecution.setExitMessage(START_MESSAGE);
|
||||
}
|
||||
|
||||
@AfterTask
|
||||
public void methodB(TaskExecution taskExecution) {
|
||||
isTaskEnd = true;
|
||||
this.taskExecution = taskExecution;
|
||||
this.taskExecution.setExitMessage(END_MESSAGE);
|
||||
|
||||
}
|
||||
|
||||
@FailedTask
|
||||
public void methodC(TaskExecution taskExecution, Throwable throwable) {
|
||||
isTaskFailed = true;
|
||||
this.taskExecution = taskExecution;
|
||||
this.throwable = throwable;
|
||||
this.taskExecution.setExitMessage(ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class DefaultTaskListenerConfiguration {
|
||||
|
||||
@Bean
|
||||
public TestTaskExecutionListener taskExecutionListener() {
|
||||
return new TestTaskExecutionListener();
|
||||
}
|
||||
|
||||
public static class TestTaskExecutionListener extends TestListener implements TaskExecutionListener {
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.repository.TaskExecution;
|
||||
|
||||
/**
|
||||
* Provides the basic infrastructure for evaluating if task listener performed
|
||||
* properly.
|
||||
*
|
||||
* @author Glenn Renfro
|
||||
*/
|
||||
public abstract class TestListener {
|
||||
public static final String START_MESSAGE = "FOO";
|
||||
public static final String ERROR_MESSAGE = "BAR";
|
||||
public static final String END_MESSAGE = "BAZ";
|
||||
|
||||
protected boolean isTaskStartup;
|
||||
protected boolean isTaskEnd;
|
||||
protected boolean isTaskFailed;
|
||||
protected TaskExecution taskExecution;
|
||||
protected Throwable throwable;
|
||||
|
||||
/**
|
||||
* Indicates if the task listener was called during task create step.
|
||||
* @return true if task listener was called during task creation, else false.
|
||||
*/
|
||||
public boolean isTaskStartup() {
|
||||
return isTaskStartup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the task listener was called during task end.
|
||||
* @return true if the task listener was called during task end, else false.
|
||||
*/
|
||||
public boolean isTaskEnd() {
|
||||
return isTaskEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the task listener was called during task failed step.
|
||||
* @return true if task listener was called during task failure, else false.
|
||||
*/
|
||||
public boolean isTaskFailed() {
|
||||
return isTaskFailed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task Execution that was updated during listener call.
|
||||
*/
|
||||
public TaskExecution getTaskExecution() {
|
||||
return taskExecution;
|
||||
}
|
||||
|
||||
/**
|
||||
* The throwable that was sent with the task if task failed.
|
||||
*/
|
||||
public Throwable getThrowable() {
|
||||
return throwable;
|
||||
}
|
||||
}
|
||||
@@ -185,3 +185,30 @@ notified for the following events:
|
||||
marking the final state of the task.
|
||||
. `onTaskFailed` - prior to the `onTaskEnd` method being invoked when an unhandled
|
||||
exception is thrown by the task.
|
||||
|
||||
Spring Cloud Task also allows a user add `TaskExecution` Listeners to methods within a bean
|
||||
by using the following method annotations:
|
||||
|
||||
. `@BeforeTask` - prior to the storing the TaskExecution into the TaskRepository
|
||||
. `@AfterTask` - prior to the updating of the TaskExecution entry in the TaskRepository
|
||||
marking the final state of the task.
|
||||
. `@FailedTask` - prior to the `@AfterTask` method being invoked when an unhandled
|
||||
exception is thrown by the task.
|
||||
|
||||
```
|
||||
public class MyBean {
|
||||
|
||||
@BeforeTask
|
||||
public void methodA(TaskExecution taskExecution) {
|
||||
}
|
||||
|
||||
@AfterTask
|
||||
public void methodB(TaskExecution taskExecution) {
|
||||
}
|
||||
|
||||
@FailedTask
|
||||
public void methodC(TaskExecution taskExecution, Throwable throwable) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user