Skip to content

Commit 8fcbdae

Browse files
committed
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
1 parent 90e6304 commit 8fcbdae

File tree

3 files changed

+96
-3
lines changed

3 files changed

+96
-3
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2002-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.remoting;
18+
19+
/**
20+
* RemoteAccessException subclass to be thrown when the execution
21+
* of the target method did not complete before a configurable
22+
* timeout, for example when a reply message was not received.
23+
* @author Stephane Nicoll
24+
* @since 4.2
25+
*/
26+
@SuppressWarnings("serial")
27+
public class RemoteTimeoutException extends RemoteAccessException {
28+
29+
/**
30+
* Constructor for RemoteTimeoutException.
31+
* @param msg the detail message
32+
*/
33+
public RemoteTimeoutException(String msg) {
34+
super(msg);
35+
}
36+
37+
/**
38+
* Constructor for RemoteTimeoutException.
39+
* @param msg the detail message
40+
* @param cause the root cause from the remoting API in use
41+
*/
42+
public RemoteTimeoutException(String msg, Throwable cause) {
43+
super(msg, cause);
44+
}
45+
}

spring-jms/src/main/java/org/springframework/jms/remoting/JmsInvokerClientInterceptor.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -40,6 +40,7 @@
4040
import org.springframework.jms.support.destination.DynamicDestinationResolver;
4141
import org.springframework.remoting.RemoteAccessException;
4242
import org.springframework.remoting.RemoteInvocationFailureException;
43+
import org.springframework.remoting.RemoteTimeoutException;
4344
import org.springframework.remoting.support.DefaultRemoteInvocationFactory;
4445
import org.springframework.remoting.support.RemoteInvocation;
4546
import org.springframework.remoting.support.RemoteInvocationFactory;
@@ -61,6 +62,7 @@
6162
*
6263
* @author Juergen Hoeller
6364
* @author James Strachan
65+
* @author Stephane Nicoll
6466
* @since 2.0
6567
* @see #setConnectionFactory
6668
* @see #setQueue
@@ -244,7 +246,12 @@ protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) thr
244246
Message requestMessage = createRequestMessage(session, invocation);
245247
con.start();
246248
Message responseMessage = doExecuteRequest(session, queueToUse, requestMessage);
247-
return extractInvocationResult(responseMessage);
249+
if (responseMessage != null) {
250+
return extractInvocationResult(responseMessage);
251+
}
252+
else {
253+
return onReceiveTimeout(invocation);
254+
}
248255
}
249256
finally {
250257
JmsUtils.closeSession(session);
@@ -362,6 +369,19 @@ protected RemoteInvocationResult extractInvocationResult(Message responseMessage
362369
return onInvalidResponse(responseMessage);
363370
}
364371

372+
/**
373+
* Callback that is invoked by {@code executeRequest} when the receive
374+
* timeout has expired for the specified {@link RemoteInvocation}
375+
* <p>By default, an {@link RemoteTimeoutException} is thrown. Sub-classes
376+
* can choose to either throw a more dedicated exception or event return
377+
* a default {@link RemoteInvocationResult} as a fallback.
378+
* @param invocation the invocation
379+
* @return a default result when the receive timeout has expired
380+
*/
381+
protected RemoteInvocationResult onReceiveTimeout(RemoteInvocation invocation) {
382+
throw new RemoteTimeoutException("Receive timeout after " + this.receiveTimeout + " ms for " + invocation);
383+
}
384+
365385
/**
366386
* Callback that is invoked by {@code extractInvocationResult}
367387
* when it encounters an invalid response message.

spring-jms/src/test/java/org/springframework/jms/remoting/JmsInvokerTests.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,10 +31,13 @@
3131
import javax.jms.Session;
3232

3333
import org.junit.Before;
34+
import org.junit.Rule;
3435
import org.junit.Test;
36+
import org.junit.rules.ExpectedException;
3537

3638
import org.springframework.jms.support.converter.MessageConversionException;
3739
import org.springframework.jms.support.converter.SimpleMessageConverter;
40+
import org.springframework.remoting.RemoteTimeoutException;
3841
import org.springframework.tests.sample.beans.ITestBean;
3942
import org.springframework.tests.sample.beans.TestBean;
4043

@@ -43,9 +46,13 @@
4346

4447
/**
4548
* @author Juergen Hoeller
49+
* @author Stephane Nicoll
4650
*/
4751
public class JmsInvokerTests {
4852

53+
@Rule
54+
public final ExpectedException thrown = ExpectedException.none();
55+
4956
private QueueConnectionFactory mockConnectionFactory;
5057

5158
private QueueConnection mockConnection;
@@ -78,6 +85,27 @@ public void testJmsInvokerProxyFactoryBeanAndServiceExporterWithDynamicQueue() t
7885
doTestJmsInvokerProxyFactoryBeanAndServiceExporter(true);
7986
}
8087

88+
@Test
89+
public void receiveTimeoutExpired() {
90+
JmsInvokerProxyFactoryBean pfb = new JmsInvokerProxyFactoryBean() {
91+
@Override
92+
protected Message doExecuteRequest(Session session, Queue queue, Message requestMessage) throws JMSException {
93+
return null; // faking no message received
94+
}
95+
};
96+
pfb.setServiceInterface(ITestBean.class);
97+
pfb.setConnectionFactory(this.mockConnectionFactory);
98+
pfb.setQueue(this.mockQueue);
99+
pfb.setReceiveTimeout(1500);
100+
pfb.afterPropertiesSet();
101+
ITestBean proxy = (ITestBean) pfb.getObject();
102+
103+
thrown.expect(RemoteTimeoutException.class);
104+
thrown.expectMessage("1500 ms");
105+
thrown.expectMessage("getAge");
106+
proxy.getAge();
107+
}
108+
81109
private void doTestJmsInvokerProxyFactoryBeanAndServiceExporter(boolean dynamicQueue) throws Throwable {
82110
TestBean target = new TestBean("myname", 99);
83111

0 commit comments

Comments
 (0)