From 8fcbdaee24ffefaa4e73209c61cf76e8f4fbce2b Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 19 Feb 2015 16:51:41 +0100 Subject: [PATCH] Handle receive timeout in JmsInvokerClientInterceptor JmsInvokerClientInterceptor defines a receiveTimeout field but does not handle such timeout. This commit adds an additional callback that throws an RemoteTimeoutException instead. Sub-classes can override the onReceiveTimeout to throw a different exception or return a fallback RemoteInvocationResult. Issue: SPR-12731 --- .../remoting/RemoteTimeoutException.java | 45 +++++++++++++++++++ .../remoting/JmsInvokerClientInterceptor.java | 24 +++++++++- .../jms/remoting/JmsInvokerTests.java | 30 ++++++++++++- 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 spring-context/src/main/java/org/springframework/remoting/RemoteTimeoutException.java diff --git a/spring-context/src/main/java/org/springframework/remoting/RemoteTimeoutException.java b/spring-context/src/main/java/org/springframework/remoting/RemoteTimeoutException.java new file mode 100644 index 0000000000..9773a80772 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/remoting/RemoteTimeoutException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-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.remoting; + +/** + * RemoteAccessException subclass to be thrown when the execution + * of the target method did not complete before a configurable + * timeout, for example when a reply message was not received. + * @author Stephane Nicoll + * @since 4.2 + */ +@SuppressWarnings("serial") +public class RemoteTimeoutException extends RemoteAccessException { + + /** + * Constructor for RemoteTimeoutException. + * @param msg the detail message + */ + public RemoteTimeoutException(String msg) { + super(msg); + } + + /** + * Constructor for RemoteTimeoutException. + * @param msg the detail message + * @param cause the root cause from the remoting API in use + */ + public RemoteTimeoutException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/spring-jms/src/main/java/org/springframework/jms/remoting/JmsInvokerClientInterceptor.java b/spring-jms/src/main/java/org/springframework/jms/remoting/JmsInvokerClientInterceptor.java index 1de257e109..b70e6743c0 100644 --- a/spring-jms/src/main/java/org/springframework/jms/remoting/JmsInvokerClientInterceptor.java +++ b/spring-jms/src/main/java/org/springframework/jms/remoting/JmsInvokerClientInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-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. @@ -40,6 +40,7 @@ import org.springframework.jms.support.destination.DestinationResolver; import org.springframework.jms.support.destination.DynamicDestinationResolver; import org.springframework.remoting.RemoteAccessException; import org.springframework.remoting.RemoteInvocationFailureException; +import org.springframework.remoting.RemoteTimeoutException; import org.springframework.remoting.support.DefaultRemoteInvocationFactory; import org.springframework.remoting.support.RemoteInvocation; import org.springframework.remoting.support.RemoteInvocationFactory; @@ -61,6 +62,7 @@ import org.springframework.remoting.support.RemoteInvocationResult; * * @author Juergen Hoeller * @author James Strachan + * @author Stephane Nicoll * @since 2.0 * @see #setConnectionFactory * @see #setQueue @@ -244,7 +246,12 @@ public class JmsInvokerClientInterceptor implements MethodInterceptor, Initializ Message requestMessage = createRequestMessage(session, invocation); con.start(); Message responseMessage = doExecuteRequest(session, queueToUse, requestMessage); - return extractInvocationResult(responseMessage); + if (responseMessage != null) { + return extractInvocationResult(responseMessage); + } + else { + return onReceiveTimeout(invocation); + } } finally { JmsUtils.closeSession(session); @@ -362,6 +369,19 @@ public class JmsInvokerClientInterceptor implements MethodInterceptor, Initializ return onInvalidResponse(responseMessage); } + /** + * Callback that is invoked by {@code executeRequest} when the receive + * timeout has expired for the specified {@link RemoteInvocation} + *

By default, an {@link RemoteTimeoutException} is thrown. Sub-classes + * can choose to either throw a more dedicated exception or event return + * a default {@link RemoteInvocationResult} as a fallback. + * @param invocation the invocation + * @return a default result when the receive timeout has expired + */ + protected RemoteInvocationResult onReceiveTimeout(RemoteInvocation invocation) { + throw new RemoteTimeoutException("Receive timeout after " + this.receiveTimeout + " ms for " + invocation); + } + /** * Callback that is invoked by {@code extractInvocationResult} * when it encounters an invalid response message. diff --git a/spring-jms/src/test/java/org/springframework/jms/remoting/JmsInvokerTests.java b/spring-jms/src/test/java/org/springframework/jms/remoting/JmsInvokerTests.java index 661c7b37e2..e023481c08 100644 --- a/spring-jms/src/test/java/org/springframework/jms/remoting/JmsInvokerTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/remoting/JmsInvokerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-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. @@ -31,10 +31,13 @@ import javax.jms.QueueSession; import javax.jms.Session; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.jms.support.converter.MessageConversionException; import org.springframework.jms.support.converter.SimpleMessageConverter; +import org.springframework.remoting.RemoteTimeoutException; import org.springframework.tests.sample.beans.ITestBean; import org.springframework.tests.sample.beans.TestBean; @@ -43,9 +46,13 @@ import static org.mockito.BDDMockito.*; /** * @author Juergen Hoeller + * @author Stephane Nicoll */ public class JmsInvokerTests { + @Rule + public final ExpectedException thrown = ExpectedException.none(); + private QueueConnectionFactory mockConnectionFactory; private QueueConnection mockConnection; @@ -78,6 +85,27 @@ public class JmsInvokerTests { doTestJmsInvokerProxyFactoryBeanAndServiceExporter(true); } + @Test + public void receiveTimeoutExpired() { + JmsInvokerProxyFactoryBean pfb = new JmsInvokerProxyFactoryBean() { + @Override + protected Message doExecuteRequest(Session session, Queue queue, Message requestMessage) throws JMSException { + return null; // faking no message received + } + }; + pfb.setServiceInterface(ITestBean.class); + pfb.setConnectionFactory(this.mockConnectionFactory); + pfb.setQueue(this.mockQueue); + pfb.setReceiveTimeout(1500); + pfb.afterPropertiesSet(); + ITestBean proxy = (ITestBean) pfb.getObject(); + + thrown.expect(RemoteTimeoutException.class); + thrown.expectMessage("1500 ms"); + thrown.expectMessage("getAge"); + proxy.getAge(); + } + private void doTestJmsInvokerProxyFactoryBeanAndServiceExporter(boolean dynamicQueue) throws Throwable { TestBean target = new TestBean("myname", 99); -- GitLab