diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/executor/ErrorHandlingExecutorBeanPostProcessor.java b/org.springframework.integration/src/main/java/org/springframework/integration/executor/ErrorHandlingExecutorBeanPostProcessor.java new file mode 100644 index 0000000000..61c5e6e314 --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/executor/ErrorHandlingExecutorBeanPostProcessor.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2008 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.integration.executor; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.integration.util.ErrorHandler; + +/** + * + * @author Jonas Partner + * + */ +public class ErrorHandlingExecutorBeanPostProcessor implements BeanPostProcessor { + + private final RunnableProxyingMethodInterceptor runnableProxyingInterceptor; + + private volatile List beanNamesToExclude = new ArrayList(); + + public ErrorHandlingExecutorBeanPostProcessor(ErrorHandler errorHandler) { + this.runnableProxyingInterceptor = new RunnableProxyingMethodInterceptor(errorHandler); + } + + public void setTaskExecutorBeanNameExclusions(List beanNamesToExclude) { + this.beanNamesToExclude = beanNamesToExclude; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof TaskExecutor && !beanNamesToExclude.contains(beanName)) { + ProxyFactory proxyFactory = new ProxyFactory(bean); + proxyFactory.addAdvisor(new DefaultPointcutAdvisor(this.runnableProxyingInterceptor)); + return proxyFactory.getProxy(); + } + return bean; + } + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/executor/RunnableProxyingMethodInterceptor.java b/org.springframework.integration/src/main/java/org/springframework/integration/executor/RunnableProxyingMethodInterceptor.java new file mode 100644 index 0000000000..4a9f384cbc --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/executor/RunnableProxyingMethodInterceptor.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2008 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.integration.executor; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.core.task.TaskExecutor; +import org.springframework.integration.util.ErrorHandler; + +/** + * Used tby {@link ErrorHandlingExecutorBeanPostProcessor} to Proxy + * {@link TaskExecutor} instances. This allow the wrapping of {@link Runnable} + * instances in order to handle catch errors thrown and delegate handling to an + * instance of {@link ErrorHandler} + * @author Jonas Partner + * + */ +public class RunnableProxyingMethodInterceptor implements MethodInterceptor { + + private final ErrorHandler errorHandler; + + public RunnableProxyingMethodInterceptor(ErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + public Object invoke(MethodInvocation invocation) throws Throwable { + Runnable runnable = (Runnable) invocation.getArguments()[0]; + ProxyFactory factory = new ProxyFactory(runnable); + factory.addAdvice(new ErrorHandlingAdvice(errorHandler)); + invocation.getArguments()[0] = factory.getProxy(); + return invocation.proceed(); + } + + private static class ErrorHandlingAdvice implements MethodInterceptor { + + private final ErrorHandler errorHandler; + + public ErrorHandlingAdvice(ErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + public Object invoke(MethodInvocation invocation) throws Throwable { + try { + invocation.proceed(); + } + catch (Throwable t) { + errorHandler.handle(t); + } + return null; + } + + } + +} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/executor/ErrorHandlingExecutorBeanPostProcessorTests-context.xml b/org.springframework.integration/src/test/java/org/springframework/integration/executor/ErrorHandlingExecutorBeanPostProcessorTests-context.xml new file mode 100644 index 0000000000..9c473f9c56 --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/executor/ErrorHandlingExecutorBeanPostProcessorTests-context.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + excludeFromProxyingTaskExecutor + + + + + + + diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/executor/ErrorHandlingExecutorBeanPostProcessorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/executor/ErrorHandlingExecutorBeanPostProcessorTests.java new file mode 100644 index 0000000000..8b92e6cfd5 --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/executor/ErrorHandlingExecutorBeanPostProcessorTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2008 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.integration.executor; + +import static org.junit.Assert.*; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.task.TaskExecutor; + +/** + * + * @author Jonas Partner + * + */ +public class ErrorHandlingExecutorBeanPostProcessorTests { + + StubErrorHandler errorHandler; + + ApplicationContext applicationContext; + + @Before + public void setUp(){ + applicationContext = new ClassPathXmlApplicationContext(getClass().getSimpleName()+"-context.xml", getClass()); + errorHandler = (StubErrorHandler)applicationContext.getBean("stubErrorHandler"); + } + + @Test + public void testProxiedTaskExecutor() throws Exception{ + TaskExecutor taskExecutor = (TaskExecutor)applicationContext.getBean("proxiedTaskExecutor"); + ErrorThrowingRunnable runnable = new ErrorThrowingRunnable(); + taskExecutor.execute(runnable); + assertTrue("Runnable faield to run",runnable.latch.await(2, TimeUnit.SECONDS)); + assertEquals("Incorrect count of exceptions", 1, errorHandler.throwables.size()); + } + + @Test + public void testExcludedFromProxyingTaskExecutor() throws Exception{ + TaskExecutor taskExecutor = (TaskExecutor)applicationContext.getBean("excludeFromProxyingTaskExecutor"); + ErrorThrowingRunnable runnable = new ErrorThrowingRunnable(); + taskExecutor.execute(runnable); + runnable.latch.await(2, TimeUnit.SECONDS); + assertTrue("Runnable faield to run",runnable.latch.await(2, TimeUnit.SECONDS)); + assertEquals("Incorrect count of exceptions", 0, errorHandler.throwables.size()); + } + + private static class ErrorThrowingRunnable implements Runnable{ + + CountDownLatch latch = new CountDownLatch(1); + + public void run() { + latch.countDown(); + throw new RuntimeException(); + + } + + } +} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/executor/RunnableProxyingMethodInterceptorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/executor/RunnableProxyingMethodInterceptorTests.java new file mode 100644 index 0000000000..b7fe52ef03 --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/executor/RunnableProxyingMethodInterceptorTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2008 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.integration.executor; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.integration.util.ErrorHandler; + +/** + * + * @author Jonas Partner + * + */ +public class RunnableProxyingMethodInterceptorTests { + + TaskExecutor proxiedExecutor; + + StubErrorHandler errorHandler; + + @Before + public void setUp() { + errorHandler = new StubErrorHandler(); + ProxyFactory factory = new ProxyFactory(new SimpleAsyncTaskExecutor()); + factory.addAdvice(new RunnableProxyingMethodInterceptor(errorHandler)); + proxiedExecutor = (TaskExecutor) factory.getProxy(); + } + + @Test + public void testRuntimeThrown() throws Exception { + TestRunnable runnable = new TestRunnable(); + + proxiedExecutor.execute(runnable); + assertTrue("Runnable did not run", runnable.countDown.await(2, TimeUnit.SECONDS)); + assertEquals("Wrong count of exceptions in ErrorHandler", 1, errorHandler.throwables.size()); + } + + public static class StubErrorHandler implements ErrorHandler { + + List throwables = new ArrayList(); + + public void handle(Throwable t) { + throwables.add(t); + } + } + + public static class TestRunnable implements Runnable { + CountDownLatch countDown = new CountDownLatch(1); + + public void run() { + countDown.countDown(); + throw new RuntimeException(); + } + } + +} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/executor/StubErrorHandler.java b/org.springframework.integration/src/test/java/org/springframework/integration/executor/StubErrorHandler.java new file mode 100644 index 0000000000..c20ac457f6 --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/executor/StubErrorHandler.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2008 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.integration.executor; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.integration.util.ErrorHandler; + +public class StubErrorHandler implements ErrorHandler { + + List throwables = new ArrayList(); + + public void handle(Throwable t) { + throwables.add(t); + } + + +}