diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Gateway.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Gateway.java index 993c9a1e54..b0c15a1f65 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Gateway.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Gateway.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -28,21 +28,22 @@ import java.lang.annotation.Target; * or message payload. These method-level annotations are detected by the * {@link org.springframework.integration.gateway.GatewayProxyFactoryBean} * where the annotation attributes can override the default channel settings. - * + * *

A method annotated with @Gateway may accept a single non-annotated * parameter of type {@link org.springframework.integration.Message} * or of the intended Message payload type. Method parameters may be mapped * to individual Message header values by using the {@link Header @Header} * parameter annotation. Alternatively, to pass the entire Message headers * map, a Map-typed parameter may be annotated with {@link Headers}. - * + * *

Return values from the annotated method may be of any type. If the * declared return value is not a Message, the reply Message's payload will be * returned and any type conversion as supported by Spring's * {@link org.springframework.beans.SimpleTypeConverter} will be applied to * the return value if necessary. - * + * * @author Mark Fisher + * @author Gary Russell */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @@ -54,8 +55,8 @@ public @interface Gateway { String replyChannel() default ""; - long requestTimeout() default -1; + long requestTimeout() default Long.MIN_VALUE; - long replyTimeout() default -1; + long replyTimeout() default Long.MIN_VALUE; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java index 4638252dd6..13a45b190f 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -58,14 +58,15 @@ import org.springframework.util.StringUtils; /** * Generates a proxy for the provided service interface to enable interaction - * with messaging components without application code being aware of them allowing - * for POJO-style interaction. + * with messaging components without application code being aware of them allowing + * for POJO-style interaction. * This component is also aware of the {@link ConversionService} set on the enclosing {@link BeanFactory} - * under the name {@link IntegrationContextUtils#INTEGRATION_CONVERSION_SERVICE_BEAN_NAME} to + * under the name {@link IntegrationContextUtils#INTEGRATION_CONVERSION_SERVICE_BEAN_NAME} to * perform type conversions when necessary (thanks to Jon Schneider's contribution and suggestion in INT-1230). - * + * * @author Mark Fisher * @author Oleg Zhurakousky + * @author Gary Russell */ public class GatewayProxyFactoryBean extends AbstractEndpoint implements TrackableComponent, FactoryBean, MethodInterceptor, BeanClassLoaderAware { @@ -77,9 +78,9 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab private volatile MessageChannel errorChannel; - private volatile long defaultRequestTimeout = -1; + private volatile Long defaultRequestTimeout; - private volatile long defaultReplyTimeout = -1; + private volatile Long defaultReplyTimeout; private volatile ChannelResolver channelResolver; @@ -130,7 +131,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab /** * Set the default request channel. - * + * * @param defaultRequestChannel the channel to which request messages will * be sent if no request channel has been configured with an annotation */ @@ -142,7 +143,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab * Set the default reply channel. If no default reply channel is provided, * and no reply channel is configured with annotations, an anonymous, * temporary channel will be used for handling replies. - * + * * @param defaultReplyChannel the channel from which reply messages will be * received if no reply channel has been configured with an annotation */ @@ -162,7 +163,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab /** * Set the default timeout value for sending request messages. If not * explicitly configured with an annotation, this value will be used. - * + * * @param defaultRequestTimeout the timeout value in milliseconds */ public void setDefaultRequestTimeout(long defaultRequestTimeout) { @@ -172,7 +173,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab /** * Set the default timeout value for receiving reply messages. If not * explicitly configured with an annotation, this value will be used. - * + * * @param defaultReplyTimeout the timeout value in milliseconds */ public void setDefaultReplyTimeout(long defaultReplyTimeout) { @@ -202,7 +203,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab public void setMethodMetadataMap(Map methodMetadataMap) { this.methodMetadataMap = methodMetadataMap; } - + public void setBeanClassLoader(ClassLoader beanClassLoader) { this.beanClassLoader = beanClassLoader; } @@ -336,8 +337,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab Gateway gatewayAnnotation = method.getAnnotation(Gateway.class); MessageChannel requestChannel = this.defaultRequestChannel; MessageChannel replyChannel = this.defaultReplyChannel; - long requestTimeout = this.defaultRequestTimeout; - long replyTimeout = this.defaultReplyTimeout; + Long requestTimeout = this.defaultRequestTimeout; + Long replyTimeout = this.defaultReplyTimeout; String payloadExpression = null; Map headerExpressions = null; if (gatewayAnnotation != null) { @@ -349,11 +350,22 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab if (StringUtils.hasText(replyChannelName)) { replyChannel = this.resolveChannelName(replyChannelName); } - requestTimeout = gatewayAnnotation.requestTimeout(); - replyTimeout = gatewayAnnotation.replyTimeout(); + /* + * INT-2636 Unspecified annotation attributes should not + * override the default values supplied by explicit configuration. + * There is a small risk that someone has used Long.MIN_VALUE explicitly + * to indicate an indefinite timeout on a gateway method and that will + * no longer work as expected; they will need to use, say, -1 instead. + */ + if (requestTimeout == null || gatewayAnnotation.requestTimeout() != Long.MIN_VALUE) { + requestTimeout = gatewayAnnotation.requestTimeout(); + } + if (replyTimeout == null || gatewayAnnotation.replyTimeout() != Long.MIN_VALUE) { + replyTimeout = gatewayAnnotation.replyTimeout(); + } } - else if (methodMetadataMap != null && methodMetadataMap.size() > 0) { - GatewayMethodMetadata methodMetadata = methodMetadataMap.get(method.getName()); + else if (methodMetadataMap != null && methodMetadataMap.size() > 0) { + GatewayMethodMetadata methodMetadata = methodMetadataMap.get(method.getName()); if (methodMetadata != null) { payloadExpression = methodMetadata.getPayloadExpression(); headerExpressions = methodMetadata.getHeaderExpressions(); @@ -388,8 +400,18 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab gateway.setBeanName(this.getComponentName()); gateway.setRequestChannel(requestChannel); gateway.setReplyChannel(replyChannel); - gateway.setRequestTimeout(requestTimeout); - gateway.setReplyTimeout(replyTimeout); + if (requestTimeout == null) { + gateway.setRequestTimeout(-1); + } + else { + gateway.setRequestTimeout(requestTimeout); + } + if (replyTimeout == null) { + gateway.setReplyTimeout(-1); + } + else { + gateway.setReplyTimeout(replyTimeout); + } if (this.getBeanFactory() != null) { gateway.setBeanFactory(this.getBeanFactory()); } @@ -399,7 +421,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab gateway.afterPropertiesSet(); return gateway; } - + private MessageChannel resolveChannelName(String channelName) { Assert.state(this.channelResolver != null, "ChannelResolver is required"); MessageChannel channel = this.channelResolver.resolveChannelName(channelName); @@ -471,7 +493,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab private AsyncInvocationTask(MethodInvocation invocation) { this.invocation = invocation; } - + public Object call() throws Exception { try { return doInvoke(this.invocation); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayXmlAndAnnotationTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayXmlAndAnnotationTests-context.xml new file mode 100644 index 0000000000..086972ee13 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayXmlAndAnnotationTests-context.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayXmlAndAnnotationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayXmlAndAnnotationTests.java new file mode 100644 index 0000000000..0b06b08591 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayXmlAndAnnotationTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2012 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.gateway; + +import static org.junit.Assert.assertEquals; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Map.Entry; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.annotation.Gateway; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Gary Russell + * @since 2.2 + * + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class GatewayXmlAndAnnotationTests { + + @Autowired + GatewayProxyFactoryBean gatewayProxyFactoryBean; + + @Test + public void test() { + assertEquals(123L, TestUtils.getPropertyValue(gatewayProxyFactoryBean, "defaultReplyTimeout")); + @SuppressWarnings("unchecked") + Map gatewayMap = TestUtils.getPropertyValue(gatewayProxyFactoryBean, + "gatewayMap", Map.class); + int assertions = 0; + for (Entry entry : gatewayMap.entrySet()) { + if (entry.getKey().getName().equals("annotationShouldntOverrideDefault")) { + assertEquals(123L, TestUtils.getPropertyValue(entry.getValue(), + "replyTimeout")); + assertions++; + } + else if (entry.getKey().getName().equals("annotationShouldOverrideDefault")) { + assertEquals(234L, TestUtils.getPropertyValue(entry.getValue(), + "replyTimeout")); + assertions++; + } + else if (entry.getKey().getName().equals("annotationShouldOverrideDefaultToInfinity")) { + assertEquals(-1L, TestUtils.getPropertyValue(entry.getValue(), + "replyTimeout")); + assertions++; + } + else if (entry.getKey().getName().equals("explicitTimeoutShouldOverrideDefault")) { + assertEquals(456L, TestUtils.getPropertyValue(entry.getValue(), + "replyTimeout")); + assertions++; + } + } + assertEquals(4, assertions); + } + + public static interface AGateway { + @Gateway + String annotationShouldntOverrideDefault(String foo); + + @Gateway(replyTimeout=234) + String annotationShouldOverrideDefault(String foo); + + @Gateway(replyTimeout=-1) + String annotationShouldOverrideDefaultToInfinity(String foo); + + String explicitTimeoutShouldOverrideDefault(String foo); + } +}