diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingGatewayRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingGatewayRegistrar.java index 52e68113a3..97c5500696 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingGatewayRegistrar.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingGatewayRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 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. @@ -24,7 +24,6 @@ import java.util.Map.Entry; import java.util.Set; import org.springframework.beans.BeanMetadataAttribute; -import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; @@ -39,12 +38,10 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.expression.common.LiteralExpression; import org.springframework.integration.annotation.AnnotationConstants; import org.springframework.integration.annotation.MessagingGateway; -import org.springframework.integration.gateway.GatewayCompletableFutureProxyFactoryBean; import org.springframework.integration.gateway.GatewayMethodMetadata; import org.springframework.integration.gateway.GatewayProxyFactoryBean; import org.springframework.integration.util.MessagingAnnotationUtils; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -56,21 +53,16 @@ import org.springframework.util.StringUtils; * @author Artem Bilan * @author Gary Russell * @author Andy Wilksinson + * * @since 4.0 */ -public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware { - - private ClassLoader beanClassLoader; - - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - this.beanClassLoader = classLoader; - } +public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (importingClassMetadata != null && importingClassMetadata.isAnnotated(MessagingGateway.class.getName())) { - Assert.isTrue(importingClassMetadata.isInterface(), "@MessagingGateway can only be specified on an interface"); + Assert.isTrue(importingClassMetadata.isInterface(), + "@MessagingGateway can only be specified on an interface"); List> valuesHierarchy = captureMetaAnnotationValues(importingClassMetadata); Map annotationAttributes = importingClassMetadata.getAnnotationAttributes(MessagingGateway.class.getName()); @@ -82,14 +74,6 @@ public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar, } public BeanDefinitionHolder parse(Map gatewayAttributes) { - boolean completableFutureCapable = true; - try { - ClassUtils.forName("java.util.concurrent.CompletableFuture", this.beanClassLoader); - } - catch (Exception e1) { - completableFutureCapable = false; - } - String defaultPayloadExpression = (String) gatewayAttributes.get("defaultPayloadExpression"); @SuppressWarnings("unchecked") @@ -107,14 +91,15 @@ public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar, "'defaultPayloadExpression' is not allowed when a 'mapper' is provided"); boolean hasDefaultHeaders = !ObjectUtils.isEmpty(defaultHeaders); - Assert.state(!hasMapper || !hasDefaultHeaders, "'defaultHeaders' are not allowed when a 'mapper' is provided"); + Assert.state(!hasMapper || !hasDefaultHeaders, + "'defaultHeaders' are not allowed when a 'mapper' is provided"); - BeanDefinitionBuilder gatewayProxyBuilder = BeanDefinitionBuilder.genericBeanDefinition( - completableFutureCapable ? GatewayCompletableFutureProxyFactoryBean.class - : GatewayProxyFactoryBean.class); + BeanDefinitionBuilder gatewayProxyBuilder = + BeanDefinitionBuilder.genericBeanDefinition(GatewayProxyFactoryBean.class); if (hasDefaultHeaders || hasDefaultPayloadExpression) { - BeanDefinitionBuilder methodMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition(GatewayMethodMetadata.class); + BeanDefinitionBuilder methodMetadataBuilder = + BeanDefinitionBuilder.genericBeanDefinition(GatewayMethodMetadata.class); if (hasDefaultPayloadExpression) { methodMetadataBuilder.addPropertyValue("payloadExpression", defaultPayloadExpression); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/dsl/IntegrationFlowBeanPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/dsl/IntegrationFlowBeanPostProcessor.java index ac4b5f613b..c60341be02 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/dsl/IntegrationFlowBeanPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/dsl/IntegrationFlowBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -51,6 +51,7 @@ import org.springframework.integration.dsl.IntegrationFlows; import org.springframework.integration.dsl.SourcePollingChannelAdapterSpec; import org.springframework.integration.dsl.StandardIntegrationFlow; import org.springframework.integration.dsl.support.MessageChannelReference; +import org.springframework.integration.gateway.AnnotationGatewayProxyFactoryBean; import org.springframework.integration.support.context.NamedComponent; import org.springframework.messaging.MessageHandler; import org.springframework.util.Assert; @@ -231,6 +232,9 @@ public class IntegrationFlowBeanPostProcessor implements BeanPostProcessor, Bean BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + subFlowNameIndex++; registerComponent(component, subFlowBeanName, beanName, registerSingleton); } + else if (component instanceof AnnotationGatewayProxyFactoryBean) { + registerComponent(component, flowNamePrefix + "gateway", beanName, registerSingleton); + } else { String generateBeanName = generateBeanName(component); registerComponent(component, generateBeanName, beanName, registerSingleton); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlows.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlows.java index 4a3d1307fd..2fecbe601d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlows.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlows.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -19,6 +19,7 @@ package org.springframework.integration.dsl; import java.util.function.Consumer; import java.util.function.Function; +import org.springframework.integration.annotation.MessagingGateway; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.core.MessageSource; import org.springframework.integration.dsl.channel.MessageChannelSpec; @@ -26,6 +27,8 @@ import org.springframework.integration.dsl.support.FixedSubscriberChannelPrototy import org.springframework.integration.dsl.support.MessageChannelReference; import org.springframework.integration.endpoint.MessageProducerSupport; import org.springframework.integration.endpoint.MethodInvokingMessageSource; +import org.springframework.integration.gateway.AnnotationGatewayProxyFactoryBean; +import org.springframework.integration.gateway.GatewayProxyFactoryBean; import org.springframework.integration.gateway.MessagingGatewaySupport; import org.springframework.messaging.MessageChannel; import org.springframework.util.Assert; @@ -276,6 +279,36 @@ public final class IntegrationFlows { return from(inboundGateway, (IntegrationFlowBuilder) null); } + /** + * Populate the {@link MessageChannel} to the new {@link IntegrationFlowBuilder} chain, + * which becomes as a {@code requestChannel} for the Messaging Gateway(s) built on the provided + * service interface. + *

A gateway proxy bean for provided service interface is registered + * under a name of the {@link IntegrationFlow} bean plus {@code .gateway} suffix. + * @param serviceInterface the class with a {@link MessagingGateway} annotation. + * @return new {@link IntegrationFlowBuilder}. + */ + public static IntegrationFlowBuilder from(Class serviceInterface) { + final DirectChannel gatewayRequestChannel = new DirectChannel(); + + GatewayProxyFactoryBean gatewayProxyFactoryBean = + new AnnotationGatewayProxyFactoryBean(serviceInterface) { + + @Override + protected void onInit() { + super.onInit(); + getGateways() + .values() + .forEach(gateway -> + gateway.setRequestChannel(gatewayRequestChannel)); + } + + }; + + return from(gatewayRequestChannel) + .addComponent(gatewayProxyFactoryBean); + } + private static IntegrationFlowBuilder from(MessagingGatewaySupport inboundGateway, IntegrationFlowBuilder integrationFlowBuilder) { MessageChannel outputChannel = inboundGateway.getRequestChannel(); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/AnnotationGatewayProxyFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/AnnotationGatewayProxyFactoryBean.java new file mode 100644 index 0000000000..249fb6ba0c --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/AnnotationGatewayProxyFactoryBean.java @@ -0,0 +1,142 @@ +/* + * Copyright 2017 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 java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.expression.Expression; +import org.springframework.expression.common.LiteralExpression; +import org.springframework.integration.annotation.AnnotationConstants; +import org.springframework.integration.annotation.MessagingGateway; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * A {@link GatewayProxyFactoryBean} extension for Java configuration, + * when service interface is marked with the {@link MessagingGateway} annotation. + * + * @author Artem Bilan + * + * @since 5.0 + */ +public class AnnotationGatewayProxyFactoryBean extends GatewayProxyFactoryBean { + + private final AnnotationAttributes gatewayAttributes; + + public AnnotationGatewayProxyFactoryBean(Class serviceInterface) { + super(serviceInterface); + this.gatewayAttributes = AnnotatedElementUtils.getMergedAnnotationAttributes(serviceInterface, + MessagingGateway.class.getName(), false, true); + Assert.notNull(this.gatewayAttributes, + "The Messaging Gateway interface must be annotated with the @MessagingGateway"); + + } + + @Override + protected void onInit() { + ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) getBeanFactory(); + + String defaultPayloadExpression = + beanFactory.resolveEmbeddedValue( + this.gatewayAttributes.getString("defaultPayloadExpression")); + + @SuppressWarnings("unchecked") + Map[] defaultHeaders = (Map[]) this.gatewayAttributes.get("defaultHeaders"); + + String mapper = beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("mapper")); + + boolean hasMapper = StringUtils.hasText(mapper); + boolean hasDefaultPayloadExpression = StringUtils.hasText(defaultPayloadExpression); + Assert.state(!hasMapper || !hasDefaultPayloadExpression, + "'defaultPayloadExpression' is not allowed when a 'mapper' is provided"); + + boolean hasDefaultHeaders = !ObjectUtils.isEmpty(defaultHeaders); + Assert.state(!hasMapper || !hasDefaultHeaders, + "'defaultHeaders' are not allowed when a 'mapper' is provided"); + + + String defaultRequestChannel = + beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("defaultRequestChannel")); + setDefaultRequestChannelName(defaultRequestChannel); + + String defaultReplyChannel = + beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("defaultReplyChannel")); + setDefaultReplyChannelName(defaultReplyChannel); + + String errorChannel = beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("errorChannel")); + setErrorChannelName(errorChannel); + + String asyncExecutor = beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("asyncExecutor")); + if (asyncExecutor == null || AnnotationConstants.NULL.equals(asyncExecutor)) { + setAsyncExecutor(null); + } + else if (StringUtils.hasText(asyncExecutor)) { + setAsyncExecutor(beanFactory.getBean(asyncExecutor, Executor.class)); + } + + if (hasDefaultHeaders || hasDefaultPayloadExpression) { + GatewayMethodMetadata gatewayMethodMetadata = new GatewayMethodMetadata(); + + if (hasDefaultPayloadExpression) { + gatewayMethodMetadata.setPayloadExpression(defaultPayloadExpression); + } + + Map headerExpressions = new HashMap<>(); + for (Map header : defaultHeaders) { + String headerValue = beanFactory.resolveEmbeddedValue((String) header.get("value")); + boolean hasValue = StringUtils.hasText(headerValue); + + String headerExpression = beanFactory.resolveEmbeddedValue((String) header.get("expression")); + + Assert.state(!(hasValue == StringUtils.hasText(headerExpression)), + "exactly one of 'value' or 'expression' is required on a gateway's header."); + + Expression expression = hasValue ? + new LiteralExpression(headerValue) : + EXPRESSION_PARSER.parseExpression(headerExpression); + + String headerName = beanFactory.resolveEmbeddedValue((String) header.get("name")); + headerExpressions.put(headerName, expression); + } + + gatewayMethodMetadata.setHeaderExpressions(headerExpressions); + + setGlobalMethodMetadata(gatewayMethodMetadata); + } + + if (StringUtils.hasText(mapper)) { + setMapper(beanFactory.getBean(mapper, MethodArgsMessageMapper.class)); + } + + String defaultRequestTimeout = + beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("defaultRequestTimeout")); + setDefaultRequestTimeout(Long.parseLong(defaultRequestTimeout)); + + String defaultReplyTimeout = + beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("defaultReplyTimeout")); + setDefaultReplyTimeout(Long.parseLong(defaultReplyTimeout)); + + super.onInit(); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayCompletableFutureProxyFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayCompletableFutureProxyFactoryBean.java deleted file mode 100644 index 4c939c6ea3..0000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayCompletableFutureProxyFactoryBean.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2015-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.integration.gateway; - -import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; - -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.core.task.AsyncTaskExecutor; -import org.springframework.messaging.MessagingException; - - -/** - * A gateway proxy factory that can handle JDK8 {@link CompletableFuture}s. If a - * gateway method returns {@link CompletableFuture} exactly, one will be returned and the - * results of the - * {@link CompletableFuture#supplyAsync(Supplier, java.util.concurrent.Executor)} method - * call will be returned. - *

- * If you wish your integration flow to return a {@link CompletableFuture} to the gateway - * in a reply message, the async executor must be set to {@code null}. - *

- * If the return type is a subclass of {@link CompletableFuture}, - * it must be returned by the integration flow and the async executor (if present) is not - * used. - * - * @author Gary Russell - * @since 4.2 - * - */ -public class GatewayCompletableFutureProxyFactoryBean extends GatewayProxyFactoryBean { - - public GatewayCompletableFutureProxyFactoryBean() { - super(); - } - - public GatewayCompletableFutureProxyFactoryBean(Class serviceInterface) { - super(serviceInterface); - } - - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - final Class returnType = invocation.getMethod().getReturnType(); - if (CompletableFuture.class.equals(returnType)) { // exact - AsyncTaskExecutor asyncExecutor = getAsyncExecutor(); - if (asyncExecutor != null) { - return CompletableFuture.supplyAsync(new Invoker(invocation), asyncExecutor); - } - } - return super.invoke(invocation); - } - - private final class Invoker implements Supplier { - - private final MethodInvocation invocation; - - Invoker(MethodInvocation methodInvocation) { - this.invocation = methodInvocation; - } - - @Override - public Object get() { - try { - return doInvoke(this.invocation, false); - } - catch (Error e) { //NOSONAR - throw e; - } - catch (Throwable t) { //NOSONAR - if (t instanceof RuntimeException) { - throw (RuntimeException) t; - } - throw new MessagingException("asynchronous gateway invocation failed", t); - } - } - - } - -} 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 d12b9460dc..c1377f88cd 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-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -24,8 +24,10 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.Future; +import java.util.function.Supplier; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -111,7 +113,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint private volatile Object serviceProxy; - private final Map gatewayMap = new HashMap(); + private final Map gatewayMap = new HashMap<>(); private volatile AsyncTaskExecutor asyncExecutor = new SimpleAsyncTaskExecutor(); @@ -304,7 +306,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint * @since 4.3 */ public Map getGateways() { - return Collections.unmodifiableMap(this.gatewayMap); + return Collections.unmodifiableMap(this.gatewayMap); } @Override @@ -323,7 +325,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint MethodInvocationGateway gateway = this.createGatewayForMethod(method); this.gatewayMap.put(method, gateway); } - this.serviceProxy = new ProxyFactory(proxyInterface, this).getProxy(this.beanClassLoader); + this.serviceProxy = new ProxyFactory(proxyInterface, this) + .getProxy(this.beanClassLoader); if (this.asyncExecutor != null) { Callable task = () -> null; Future submitType = this.asyncExecutor.submit(task); @@ -368,11 +371,15 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint public Object invoke(final MethodInvocation invocation) throws Throwable { final Class returnType = invocation.getMethod().getReturnType(); if (this.asyncExecutor != null && !Object.class.equals(returnType)) { + Invoker invoker = new Invoker(invocation); if (returnType.isAssignableFrom(this.asyncSubmitType)) { - return this.asyncExecutor.submit(new AsyncInvocationTask(invocation)); + return this.asyncExecutor.submit(invoker::get); } else if (returnType.isAssignableFrom(this.asyncSubmitListenableType)) { - return ((AsyncListenableTaskExecutor) this.asyncExecutor).submitListenable(new AsyncInvocationTask(invocation)); + return ((AsyncListenableTaskExecutor) this.asyncExecutor).submitListenable(invoker::get); + } + else if (CompletableFuture.class.equals(returnType)) { // exact + return CompletableFuture.supplyAsync(invoker, this.asyncExecutor); } else if (Future.class.isAssignableFrom(returnType)) { if (logger.isDebugEnabled()) { @@ -383,7 +390,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint } } if (Mono.class.isAssignableFrom(returnType)) { - return Mono.fromCallable(new AsyncInvocationTask(invocation)); + return Mono.fromSupplier(new Invoker(invocation)); } return this.doInvoke(invocation, true); } @@ -661,17 +668,16 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint } - - private final class AsyncInvocationTask implements Callable { + private final class Invoker implements Supplier { private final MethodInvocation invocation; - AsyncInvocationTask(MethodInvocation invocation) { - this.invocation = invocation; + Invoker(MethodInvocation methodInvocation) { + this.invocation = methodInvocation; } @Override - public Object call() throws Exception { + public Object get() { try { return doInvoke(this.invocation, false); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/GatewayParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/GatewayParserTests.java index 1cac5916f1..8ddbc32066 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/GatewayParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/GatewayParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -169,7 +169,7 @@ public class GatewayParserTests { } @Test - public void testPromiseGateway() throws Exception { + public void testMonoGateway() throws Exception { PollableChannel requestChannel = context.getBean("requestChannel", PollableChannel.class); MessageChannel replyChannel = context.getBean("replyChannel", MessageChannel.class); this.startResponder(requestChannel, replyChannel); @@ -280,7 +280,7 @@ public class GatewayParserTests { } @Test - public void testAsyncCompletableMessge() throws Exception { + public void testAsyncCompletableMessage() throws Exception { QueueChannel requestChannel = (QueueChannel) context.getBean("requestChannel"); final AtomicReference thread = new AtomicReference<>(); requestChannel.addInterceptor(new ChannelInterceptorAdapter() { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/flows/IntegrationFlowTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/flows/IntegrationFlowTests.java index e4bf6e663b..1361470225 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/dsl/flows/IntegrationFlowTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/flows/IntegrationFlowTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -46,7 +46,6 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.integration.MessageDispatchingException; import org.springframework.integration.MessageRejectedException; -import org.springframework.integration.annotation.IntegrationComponentScan; import org.springframework.integration.annotation.MessageEndpoint; import org.springframework.integration.annotation.MessagingGateway; import org.springframework.integration.annotation.ServiceActivator; @@ -82,7 +81,7 @@ import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; /** * @author Artem Bilan @@ -91,8 +90,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * * @since 5.0 */ -@ContextConfiguration -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @DirtiesContext public class IntegrationFlowTests { @@ -418,7 +416,7 @@ public class IntegrationFlowTests { } - @MessagingGateway(defaultRequestChannel = "controlBus") + @MessagingGateway public interface ControlBusGateway { void send(String command); @@ -426,12 +424,13 @@ public class IntegrationFlowTests { @Configuration @EnableIntegration - @IntegrationComponentScan public static class ContextConfiguration { @Bean public IntegrationFlow controlBusFlow() { - return IntegrationFlows.from("controlBus").controlBus().get(); + return IntegrationFlows.from(ControlBusGateway.class) + .controlBus() + .get(); } @Bean(name = PollerMetadata.DEFAULT_POLLER) diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java index b4a7c2aa52..bfb39fef6e 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -17,6 +17,7 @@ package org.springframework.integration.gateway; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -35,6 +36,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; +import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -59,9 +61,11 @@ import org.springframework.context.annotation.FilterType; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.expression.common.LiteralExpression; import org.springframework.integration.annotation.AnnotationConstants; import org.springframework.integration.annotation.BridgeTo; import org.springframework.integration.annotation.Gateway; +import org.springframework.integration.annotation.GatewayHeader; import org.springframework.integration.annotation.IntegrationComponentScan; import org.springframework.integration.annotation.MessagingGateway; import org.springframework.integration.annotation.ServiceActivator; @@ -114,6 +118,7 @@ public class GatewayInterfaceTests { @Autowired @Qualifier("&gatewayInterfaceTests$NoExecGateway") private GatewayProxyFactoryBean noExecGatewayFB; + @Autowired private SimpleAsyncTaskExecutor exec; @@ -123,6 +128,18 @@ public class GatewayInterfaceTests { @Autowired(required = false) private NotAGatewayByScanFilter notAGatewayByScanFilter; + @Autowired(required = false) + private GatewayByAnnotationGPFB gatewayByAnnotationGPFB; + + @Autowired + @Qualifier("&annotationGatewayProxyFactoryBean") + private GatewayProxyFactoryBean annotationGatewayProxyFactoryBean; + + @Autowired + private MessageChannel gatewayChannel; + + @Autowired + private MessageChannel errorChannel; @Test public void testWithServiceSuperclassAnnotatedMethod() throws Exception { @@ -403,6 +420,40 @@ public class GatewayInterfaceTests { assertEquals("foo", this.autoCreateChannelService.service("foo")); } + @Test + @SuppressWarnings("rawtypes") + public void testAnnotationGatewayProxyFactoryBean() { + assertNotNull(this.gatewayByAnnotationGPFB); + + assertSame(this.exec, this.annotationGatewayProxyFactoryBean.getAsyncExecutor()); + assertEquals(1111L, + TestUtils.getPropertyValue(this.annotationGatewayProxyFactoryBean, "defaultRequestTimeout")); + assertEquals(222L, + TestUtils.getPropertyValue(this.annotationGatewayProxyFactoryBean, "defaultReplyTimeout")); + + Collection messagingGateways = + this.annotationGatewayProxyFactoryBean.getGateways().values(); + assertEquals(1, messagingGateways.size()); + + MessagingGatewaySupport gateway = messagingGateways.iterator().next(); + assertSame(this.gatewayChannel, gateway.getRequestChannel()); + assertSame(this.gatewayChannel, gateway.getReplyChannel()); + assertSame(this.errorChannel, gateway.getErrorChannel()); + Object requestMapper = TestUtils.getPropertyValue(gateway, "requestMapper"); + + assertEquals("@foo", + TestUtils.getPropertyValue(requestMapper, "payloadExpression.expression")); + + Map globalHeaderExpressions = TestUtils.getPropertyValue(requestMapper, "globalHeaderExpressions", Map.class); + assertEquals(1, globalHeaderExpressions.size()); + + Object barHeaderExpression = globalHeaderExpressions.get("bar"); + assertNotNull(barHeaderExpression); + assertThat(barHeaderExpression, instanceOf(LiteralExpression.class)); + assertEquals("baz", ((LiteralExpression) barHeaderExpression).getValue()); + } + + public interface Foo { @Gateway(requestChannel = "requestChannelFoo") @@ -506,6 +557,11 @@ public class GatewayInterfaceTests { return new BridgeHandler(); } + + @Bean + public GatewayProxyFactoryBean annotationGatewayProxyFactoryBean() { + return new AnnotationGatewayProxyFactoryBean(GatewayByAnnotationGPFB.class); + } } @MessagingGateway @@ -560,6 +616,24 @@ public class GatewayInterfaceTests { } + @MessagingGateway( + defaultRequestChannel = "${gateway.channel:gatewayChannel}", + defaultReplyChannel = "${gateway.channel:gatewayChannel}", + defaultPayloadExpression = "${gateway.payload:@foo}", + errorChannel = "${gateway.channel:errorChannel}", + asyncExecutor = "${gateway.executor:exec}", + defaultRequestTimeout = "${gateway.timeout:1111}", + defaultReplyTimeout = "${gateway.timeout:222}", + defaultHeaders = { + @GatewayHeader(name = "${gateway.header.name:bar}", + value = "${gateway.header.value:baz}") + }) + public interface GatewayByAnnotationGPFB { + + String foo(String payload); + + } + @Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface TestMessagingGateway {