GatewayProxyFactoryBean now creates a Map of gateways (per-Method). This will enable support for @Gateway annotations (coming soon). Also, the @Header and @Headers annotations are now supported for gateway method parameters. The 'request-channel' and 'reply-channel' attributes of the <gateway/> element have been changed to 'default-request-channel' and 'default-reply-channel' since the request/reply channels will be configurable on a method-by-method basis with annotations (the same applies to 'request-timeout' and 'reply-timeout'). The MessageMapper interface has been split into InboundMessageMapper (with 'toMessage') and OutboundMessageMapper (with 'fromMessage') since the behavior is not always symmetrical For example, the gateway uses MethodParameterMessageMapper for creating a Message *from* the args array but it uses SimpleMessageMapper (the new name for DefaultMessageMapper) to create a Message whose payload is the method's return value.

This commit is contained in:
Mark Fisher
2008-09-28 19:58:24 +00:00
parent 9afcda0a68
commit a76c65220b
18 changed files with 162 additions and 104 deletions

View File

@@ -154,11 +154,10 @@
</xsd:annotation>
<xsd:attribute name="id" type="xsd:ID" use="required"/>
<xsd:attribute name="service-interface" type="xsd:string" use="required"/>
<xsd:attribute name="request-channel" type="xsd:string"/>
<xsd:attribute name="reply-channel" type="xsd:string" use="optional"/>
<xsd:attribute name="request-timeout" type="xsd:long"/>
<xsd:attribute name="reply-timeout" type="xsd:long"/>
<xsd:attribute name="message-mapper" type="xsd:string"/>
<xsd:attribute name="default-request-channel" type="xsd:string"/>
<xsd:attribute name="default-reply-channel" type="xsd:string" use="optional"/>
<xsd:attribute name="default-request-timeout" type="xsd:long"/>
<xsd:attribute name="default-reply-timeout" type="xsd:long"/>
</xsd:complexType>
</xsd:element>

View File

@@ -16,8 +16,6 @@
package org.springframework.integration.gateway;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.ConfigurationException;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.bus.MessageBusAware;
import org.springframework.integration.channel.MessageChannel;
@@ -36,7 +34,7 @@ import org.springframework.util.Assert;
*
* @author Mark Fisher
*/
public abstract class AbstractMessagingGateway implements MessagingGateway, MessageBusAware, InitializingBean {
public abstract class AbstractMessagingGateway implements MessagingGateway, MessageBusAware {
private volatile MessageChannel requestChannel;
@@ -95,15 +93,9 @@ public abstract class AbstractMessagingGateway implements MessagingGateway, Mess
this.messageBus = messageBus;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.requestChannel, "requestChannel must not be null");
}
public void send(Object object) {
if (this.requestChannel == null) {
throw new IllegalStateException(
"send is not supported, because no request channel has been configured");
}
Assert.state(this.requestChannel != null,
"send is not supported, because no request channel has been configured");
Message<?> message = this.toMessage(object);
Assert.notNull(message, "message must not be null");
if (!this.channelTemplate.send(message, this.requestChannel)) {
@@ -112,10 +104,8 @@ public abstract class AbstractMessagingGateway implements MessagingGateway, Mess
}
public Object receive() {
if (this.replyChannel == null || !(this.replyChannel instanceof PollableChannel)) {
throw new IllegalStateException(
"no-arg receive is not supported, because no pollable reply channel has been configured");
}
Assert.state(this.replyChannel != null && (this.replyChannel instanceof PollableChannel),
"no-arg receive is not supported, because no pollable reply channel has been configured");
Message<?> message = this.channelTemplate.receive((PollableChannel) this.replyChannel);
return this.fromMessage(message);
}
@@ -154,9 +144,7 @@ public abstract class AbstractMessagingGateway implements MessagingGateway, Mess
if (this.replyMessageCorrelator != null) {
return;
}
if (this.messageBus == null) {
throw new ConfigurationException("No MessageBus available. Cannot register ReplyMessageCorrelator.");
}
Assert.state(this.messageBus != null, "No MessageBus available. Cannot register ReplyMessageCorrelator.");
ReplyMessageCorrelator correlator = new ReplyMessageCorrelator();
correlator.setBeanName("internal.correlator." + this);
correlator.setInputChannel(this.replyChannel);

View File

@@ -17,9 +17,12 @@
package org.springframework.integration.gateway;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.SimpleTypeConverter;
@@ -30,8 +33,12 @@ import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.channel.PollableChannel;
import org.springframework.integration.config.MessageBusParser;
import org.springframework.integration.endpoint.MessagingGateway;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MethodParameterMessageMapper;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -41,17 +48,28 @@ import org.springframework.util.ClassUtils;
*
* @author Mark Fisher
*/
public class GatewayProxyFactoryBean extends SimpleMessagingGateway
implements FactoryBean, MethodInterceptor, InitializingBean, BeanClassLoaderAware, BeanFactoryAware {
public class GatewayProxyFactoryBean implements FactoryBean, MethodInterceptor, InitializingBean, BeanClassLoaderAware, BeanFactoryAware {
private volatile Class<?> serviceInterface;
private volatile MessageChannel defaultRequestChannel;
private volatile PollableChannel defaultReplyChannel;
private volatile long defaultRequestTimeout = -1;
private volatile long defaultReplyTimeout = -1;
private volatile TypeConverter typeConverter = new SimpleTypeConverter();
private volatile ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private volatile Object serviceProxy;
private final Map<Method, MessagingGateway> gatewayMap = new HashMap<Method, MessagingGateway>();
private volatile MessageBus messageBus;
private volatile boolean initialized;
private final Object initializationMonitor = new Object();
@@ -64,6 +82,48 @@ public class GatewayProxyFactoryBean extends SimpleMessagingGateway
this.serviceInterface = serviceInterface;
}
/**
* Set the default request channel.
*
* @param defaulRequestChannel the channel to which request messages will
* be sent if no request channel has been configured with an annotation
*/
public void setDefaultRequestChannel(MessageChannel defaultRequestChannel) {
this.defaultRequestChannel = defaultRequestChannel;
}
/**
* 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 replyChannel the channel from which reply messages will be
* received if no reply channel has been configured with an annotation
*/
public void setDefaultReplyChannel(PollableChannel defaultReplyChannel) {
this.defaultReplyChannel = defaultReplyChannel;
}
/**
* 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) {
this.defaultRequestTimeout = defaultRequestTimeout;
}
/**
* 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) {
this.defaultReplyTimeout = defaultReplyTimeout;
}
public void setTypeConverter(TypeConverter typeConverter) {
Assert.notNull(typeConverter, "typeConverter must not be null");
this.typeConverter = typeConverter;
@@ -74,11 +134,10 @@ public class GatewayProxyFactoryBean extends SimpleMessagingGateway
}
public void setBeanFactory(BeanFactory beanFactory) {
this.setMessageBus(
(MessageBus) beanFactory.getBean(MessageBusParser.MESSAGE_BUS_BEAN_NAME));
this.messageBus = (MessageBus) beanFactory.getBean(MessageBusParser.MESSAGE_BUS_BEAN_NAME);
}
public void afterPropertiesSet() {
public void afterPropertiesSet() throws Exception {
synchronized (this.initializationMonitor) {
if (this.initialized) {
return;
@@ -86,6 +145,11 @@ public class GatewayProxyFactoryBean extends SimpleMessagingGateway
if (this.serviceInterface == null) {
throw new IllegalArgumentException("'serviceInterface' must not be null");
}
Method[] methods = this.serviceInterface.getDeclaredMethods();
for (Method method : methods) {
MessagingGateway gateway = this.createGatewayForMethod(method);
this.gatewayMap.put(method, gateway);
}
this.serviceProxy = new ProxyFactory(this.serviceInterface, this).getProxy(this.beanClassLoader);
this.initialized = true;
}
@@ -119,6 +183,7 @@ public class GatewayProxyFactoryBean extends SimpleMessagingGateway
this.afterPropertiesSet();
}
Method method = invocation.getMethod();
MessagingGateway gateway = this.gatewayMap.get(method);
Class<?> returnType = method.getReturnType();
boolean isReturnTypeMessage = Message.class.isAssignableFrom(returnType);
boolean shouldReply = returnType != void.class;
@@ -127,22 +192,34 @@ public class GatewayProxyFactoryBean extends SimpleMessagingGateway
if (paramCount == 0) {
if (shouldReply) {
if (isReturnTypeMessage) {
return this.receive();
return gateway.receive();
}
response = this.receive();
response = gateway.receive();
}
}
else {
Object payload = (paramCount == 1) ? invocation.getArguments()[0] : invocation.getArguments();
Object[] args = invocation.getArguments();
if (shouldReply) {
response = isReturnTypeMessage ? this.sendAndReceiveMessage(payload) : this.sendAndReceive(payload);
response = isReturnTypeMessage ? gateway.sendAndReceiveMessage(args) : gateway.sendAndReceive(args);
}
else {
this.send(payload);
gateway.send(args);
response = null;
}
}
return (response != null) ? this.typeConverter.convertIfNecessary(response, returnType) : null;
}
private MessagingGateway createGatewayForMethod(Method method) throws Exception {
SimpleMessagingGateway gateway = new SimpleMessagingGateway(
new MethodParameterMessageMapper(method), new SimpleMessageMapper());
gateway.setMessageBus(this.messageBus);
//TODO: get request and reply channels from annotation, else fall back to these defaults
gateway.setRequestChannel(this.defaultRequestChannel);
gateway.setReplyChannel(this.defaultReplyChannel);
gateway.setRequestTimeout(this.defaultRequestTimeout);
gateway.setReplyTimeout(this.defaultReplyTimeout);
return gateway;
}
}

View File

@@ -18,14 +18,17 @@ package org.springframework.integration.gateway;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.message.MessageMapper;
import org.springframework.integration.message.InboundMessageMapper;
import org.springframework.integration.message.OutboundMessageMapper;
/**
* A default implementation of the {@link MessageMapper} strategy interface.
* An implementation of the {@link InboundMessageMapper} and
* {@link OutboundMessageMapper} strategy interfaces that maps directly to and
* from the Message payload instance.
*
* @author Mark Fisher
*/
public class DefaultMessageMapper implements MessageMapper<Object> {
public class SimpleMessageMapper implements InboundMessageMapper<Object>, OutboundMessageMapper<Object> {
/**
* Returns the Message payload (or null if the Message is null).

View File

@@ -17,36 +17,50 @@
package org.springframework.integration.gateway;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageMapper;
import org.springframework.integration.message.InboundMessageMapper;
import org.springframework.integration.message.OutboundMessageMapper;
import org.springframework.util.Assert;
/**
* An implementation of {@link AbstractMessagingGateway} that delegates to
* a {@link MessageMapper}. The default is {@link DefaultMessageMapper}.
* an {@link InboundMessageMapper} and {@link OutboundMessageMapper}. The
* default implementation for both is {@link SimpleMessageMapper}.
*
* @see MessageMapper
* @see InboundMessageMapper
* @see OutboundMessageMapper
*
* @author Mark Fisher
*/
@SuppressWarnings("unchecked")
public class SimpleMessagingGateway extends AbstractMessagingGateway {
private volatile MessageMapper messageMapper = new DefaultMessageMapper();
private final InboundMessageMapper inboundMapper;
private final OutboundMessageMapper outboundMapper;
public void setMessageMapper(MessageMapper<?> messageMapper) {
Assert.notNull(messageMapper, "messageMapper must not be null");
this.messageMapper = (messageMapper != null)
? messageMapper : new DefaultMessageMapper();
public SimpleMessagingGateway() {
SimpleMessageMapper mapper = new SimpleMessageMapper();
this.inboundMapper = mapper;
this.outboundMapper = mapper;
}
public SimpleMessagingGateway(InboundMessageMapper<?> inboundMapper, OutboundMessageMapper<?> outboundMapper) {
Assert.notNull(inboundMapper, "InboundMessageMapper must not be null");
Assert.notNull(outboundMapper, "OutboundMessageMapper must not be null");
this.inboundMapper = inboundMapper;
this.outboundMapper = outboundMapper;
}
@Override
protected Object fromMessage(Message<?> message) {
return this.messageMapper.fromMessage(message);
return this.outboundMapper.fromMessage(message);
}
@Override
protected Message<?> toMessage(Object object) {
return this.messageMapper.toMessage(object);
return this.inboundMapper.toMessage(object);
}
}

View File

@@ -32,7 +32,7 @@ import org.springframework.util.ObjectUtils;
public class GatewayParser extends AbstractSimpleBeanDefinitionParser {
private static String[] referenceAttributes = new String[] {
"request-channel", "reply-channel", "message-mapper"
"default-request-channel", "default-reply-channel", "message-mapper"
};

View File

@@ -14,23 +14,15 @@
* limitations under the License.
*/
package org.springframework.integration.gateway.config;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageMapper;
import org.springframework.integration.message.StringMessage;
package org.springframework.integration.message;
/**
* Strategy interface for mapping from an Object to a{@link Message}.
*
* @author Mark Fisher
*/
public class TestMessageMapper implements MessageMapper<String> {
public interface InboundMessageMapper<T> {
public Message<?> toMessage(String object) {
return new StringMessage("pre." + object);
}
public String fromMessage(Message<?> message) {
return message.getPayload().toString() + ".post";
}
Message<?> toMessage(T object);
}

View File

@@ -55,7 +55,7 @@ public class MessageMappingMethodInvoker implements MethodInvoker, InitializingB
private volatile String methodName;
private volatile MessageMapper<Object[]> messageMapper;
private volatile OutboundMessageMapper<Object[]> messageMapper;
private volatile MethodInvoker invoker;

View File

@@ -48,7 +48,7 @@ import org.springframework.util.StringUtils;
*
* @author Mark Fisher
*/
public class MethodParameterMessageMapper implements MessageMapper<Object[]> {
public class MethodParameterMessageMapper implements InboundMessageMapper<Object[]>, OutboundMessageMapper<Object[]> {
private final Method method;

View File

@@ -16,15 +16,12 @@
package org.springframework.integration.message;
/**
* Strategy interface for mapping between an Object and a {@link Message}.
* Strategy interface for mapping from a {@link Message} to an Object.
*
* @author Mark Fisher
*/
public interface MessageMapper<T> {
Message<?> toMessage(T object);
public interface OutboundMessageMapper<T> {
T fromMessage(Message<?> message);

View File

@@ -28,6 +28,7 @@ import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.channel.PollableChannel;
import org.springframework.integration.channel.QueueChannel;
@@ -44,7 +45,7 @@ public class GatewayProxyFactoryBeanTests {
QueueChannel requestChannel = new QueueChannel();
startResponder(requestChannel);
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setRequestChannel(requestChannel);
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.setServiceInterface(TestService.class);
proxyFactory.afterPropertiesSet();
TestService service = (TestService) proxyFactory.getObject();
@@ -57,7 +58,7 @@ public class GatewayProxyFactoryBeanTests {
final QueueChannel requestChannel = new QueueChannel();
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setServiceInterface(TestService.class);
proxyFactory.setRequestChannel(requestChannel);
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.afterPropertiesSet();
TestService service = (TestService) proxyFactory.getObject();
service.oneWay("test");
@@ -72,7 +73,8 @@ public class GatewayProxyFactoryBeanTests {
replyChannel.send(new StringMessage("foo"));
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setServiceInterface(TestService.class);
proxyFactory.setReplyChannel(replyChannel);
proxyFactory.setDefaultRequestChannel(new DirectChannel());
proxyFactory.setDefaultReplyChannel(replyChannel);
proxyFactory.afterPropertiesSet();
TestService service = (TestService) proxyFactory.getObject();
String result = service.solicitResponse();
@@ -92,7 +94,7 @@ public class GatewayProxyFactoryBeanTests {
}).start();
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setServiceInterface(TestService.class);
proxyFactory.setRequestChannel(requestChannel);
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.afterPropertiesSet();
TestService service = (TestService) proxyFactory.getObject();
Integer result = service.requestReplyWithIntegers(123);
@@ -160,7 +162,7 @@ public class GatewayProxyFactoryBeanTests {
startResponder(requestChannel);
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setServiceInterface(TestService.class);
proxyFactory.setRequestChannel(requestChannel);
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.afterPropertiesSet();
TestService service = (TestService) proxyFactory.getObject();
String result = service.requestReplyWithMessageParameter(new StringMessage("foo"));
@@ -179,7 +181,7 @@ public class GatewayProxyFactoryBeanTests {
}).start();
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setServiceInterface(TestService.class);
proxyFactory.setRequestChannel(requestChannel);
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.afterPropertiesSet();
TestService service = (TestService) proxyFactory.getObject();
Message<?> result = service.requestReplyWithMessageReturnValue("foo");
@@ -205,6 +207,7 @@ public class GatewayProxyFactoryBeanTests {
@Test
public void testProxiedToStringMethod() throws Exception {
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setDefaultRequestChannel(new DirectChannel());
proxyFactory.setServiceInterface(TestService.class);
proxyFactory.afterPropertiesSet();
Object proxy = proxyFactory.getObject();

View File

@@ -67,17 +67,6 @@ public class GatewayParserTests {
assertEquals("foo", result);
}
@Test
public void testRequestReplyWithMessageMapper() {
ApplicationContext context = new ClassPathXmlApplicationContext("gatewayParserTests.xml", this.getClass());
PollableChannel requestChannel = (PollableChannel) context.getBean("requestChannel");
MessageChannel replyChannel = (MessageChannel) context.getBean("replyChannel");
this.startResponder(requestChannel, replyChannel);
TestService service = (TestService) context.getBean("requestReplyWithMessageMapper");
String result = service.requestReply("foo");
assertEquals("pre.foo.post", result);
}
private void startResponder(final PollableChannel requestChannel, final MessageChannel replyChannel) {
Executors.newSingleThreadExecutor().execute(new Runnable() {

View File

@@ -19,24 +19,16 @@
<gateway id="oneWay"
service-interface="org.springframework.integration.gateway.TestService"
request-channel="requestChannel"/>
default-request-channel="requestChannel"/>
<gateway id="solicitResponse"
service-interface="org.springframework.integration.gateway.TestService"
reply-channel="replyChannel"
reply-timeout="3000"/>
default-reply-channel="replyChannel"
default-reply-timeout="3000"/>
<gateway id="requestReply"
service-interface="org.springframework.integration.gateway.TestService"
request-channel="requestChannel"
reply-channel="replyChannel"/>
<gateway id="requestReplyWithMessageMapper"
service-interface="org.springframework.integration.gateway.TestService"
request-channel="requestChannel"
reply-channel="replyChannel"
message-mapper="mapper"/>
<beans:bean id="mapper" class="org.springframework.integration.gateway.config.TestMessageMapper"/>
default-request-channel="requestChannel"
default-reply-channel="replyChannel"/>
</beans:beans>

View File

@@ -15,7 +15,7 @@
<beans:bean id="proxy" class="org.springframework.integration.gateway.GatewayProxyFactoryBean">
<beans:property name="serviceInterface" value="org.springframework.integration.gateway.TestService"/>
<beans:property name="requestChannel" ref="requestChannel"/>
<beans:property name="defaultRequestChannel" ref="requestChannel"/>
</beans:bean>
<beans:bean id="testHandler" class="org.springframework.integration.gateway.TestHandler"/>

View File

@@ -22,8 +22,8 @@
<beans:bean id="proxy" class="org.springframework.integration.gateway.GatewayProxyFactoryBean">
<beans:property name="serviceInterface" value="org.springframework.integration.gateway.TestService"/>
<beans:property name="requestChannel" ref="requestChannel"/>
<beans:property name="replyChannel" ref="replyChannel"/>
<beans:property name="defaultRequestChannel" ref="requestChannel"/>
<beans:property name="defaultReplyChannel" ref="replyChannel"/>
</beans:bean>
<beans:bean id="handler" class="org.springframework.integration.gateway.TestHandler"/>