Merge remote-tracking branch 'upstream/master' into 4.0.0-WIP

Conflicts:
	spring-integration-core/src/main/java/org/springframework/integration/gateway/MessagingGatewaySupport.java
	spring-integration-core/src/main/java/org/springframework/integration/support/channel/BeanFactoryChannelResolver.java
	spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java
	spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java
	spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java
	spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusTests.java
	spring-integration-file/src/main/java/org/springframework/integration/file/DefaultFileNameGenerator.java
	spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java
	spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java
	spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java
	spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java
	spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests.java
	spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java
	spring-integration-http/src/test/java/org/springframework/integration/http/outbound/UriVariableExpressionTests.java
	spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/HelloWorldInterceptor.java
	spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java
	spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageGroupStoreTests.java
	spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageStoreTests.java
	spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests.java
	spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests.java
	spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java
	spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests.java

Resolved.
This commit is contained in:
Gary Russell
2013-11-25 17:59:51 -05:00
191 changed files with 8774 additions and 2707 deletions

View File

@@ -21,6 +21,7 @@ import java.util.concurrent.locks.Lock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.EiMessageHeaderAccessor;
import org.springframework.integration.channel.NullChannel;
@@ -127,6 +128,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
public void setMessageStore(MessageGroupStore store) {
this.messageStore = store;
store.registerMessageGroupExpiryCallback(new MessageGroupCallback() {
@Override
public void execute(MessageGroupStore messageGroupStore, MessageGroup group) {
forceComplete(group);
}
@@ -144,6 +146,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
sequenceAware = this.releaseStrategy instanceof SequenceSizeReleaseStrategy;
}
@Override
public void setOutputChannel(MessageChannel outputChannel) {
Assert.notNull(outputChannel, "'outputChannel' must not be null");
this.outputChannel = outputChannel;
@@ -268,17 +271,32 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
try {
lock.lockInterruptibly();
try {
MessageGroup groupNow = group;
/*
* Refetch the group because it might have changed while we were waiting on
* If the group argument is not already complete,
* re-fetch it because it might have changed while we were waiting on
* its lock. If the last modified timestamp changed, defer the completion
* because the selection condition may have changed such that the group
* would no longer be eligible.
* would no longer be eligible. If the timestamp changed, it's a completely new
* group and should not be reaped on this cycle.
*
* If the group argument is already complete, do not re-fetch.
* Note: not all message stores provide a direct reference to its internal
* group so the initial 'isComplete()` will only return true for those stores if
* the group was already complete at the time of its selection as a candidate.
*
* If the group is marked complete, only consider it
* for reaping if it's empty (and both timestamps are unaltered).
*/
MessageGroup groupNow = this.messageStore.getMessageGroup(
group.getGroupId());
if (!group.isComplete()) {
groupNow = this.messageStore.getMessageGroup(correlationKey);
}
long lastModifiedNow = groupNow.getLastModified();
if (group.getLastModified() == lastModifiedNow) {
if (groupNow.size() > 0) {
int groupSize = groupNow.size();
if ((!groupNow.isComplete() || groupSize == 0)
&& group.getLastModified() == lastModifiedNow
&& group.getTimestamp() == groupNow.getTimestamp()) {
if (groupSize > 0) {
if (releaseStrategy.canRelease(groupNow)) {
this.completeGroup(correlationKey, groupNow);
}
@@ -306,7 +324,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
}
}
}
finally {
finally {
if (removeGroup) {
this.remove(group);
}
@@ -347,7 +365,8 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
+ correlationKey + "] to: " + outputChannel);
}
completeGroup(correlationKey, group);
} else {
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Discarding messages of partially complete group with key ["
+ correlationKey + "] to: " + discardChannel);

View File

@@ -0,0 +1,250 @@
/*
* Copyright 2013 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.channel;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
import org.springframework.integration.support.channel.HeaderChannelRegistry;
import org.springframework.messaging.MessageChannel;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
/**
* Converts a channel to a name, retaining a reference to the channel keyed by the name.
* Allows a downstream {@link BeanFactoryChannelResolver} to find the channel by name
* in the event that the flow serialized the message at some point.
* Channels are expired after a configurable delay (60 seconds by default).
* The actual average expiry time will be 1.5x the delay.
*
* @author Gary Russell
* @since 3.0
*
*/
public class DefaultHeaderChannelRegistry extends IntegrationObjectSupport
implements HeaderChannelRegistry, SmartLifecycle, Runnable {
private static final int DEFAULT_REAPER_DELAY = 60000;
private final Map<String, MessageChannelWrapper> channels = new ConcurrentHashMap<String, DefaultHeaderChannelRegistry.MessageChannelWrapper>();
private static final AtomicLong id = new AtomicLong();
private final String uuid = UUID.randomUUID().toString() + ":";
private volatile long reaperDelay;
private volatile ScheduledFuture<?> reaperScheduledFuture;
private volatile boolean running;
private volatile int phase;
private volatile boolean autoStartup = true;
/**
* Constructs a registry with the default delay for channel expiry.
*/
public DefaultHeaderChannelRegistry() {
this(DEFAULT_REAPER_DELAY);
}
/**
* Constructs a registry with the provided delay (milliseconds) for
* channel expiry.
*
* @param reaperDelay the delay in milliseconds.
*/
public DefaultHeaderChannelRegistry(long reaperDelay) {
this.setReaperDelay(reaperDelay);
}
/**
* Set the reaper delay.
*
* @param reaperDelay the delay in milliseconds.
*/
public final void setReaperDelay(long reaperDelay) {
Assert.isTrue(reaperDelay > 0, "'reaperDelay' must be > 0");
this.reaperDelay = reaperDelay;
}
public final long getReaperDelay() {
return reaperDelay;
}
@Override
public void setTaskScheduler(TaskScheduler taskScheduler) {
super.setTaskScheduler(taskScheduler);
}
@Override
public int getPhase() {
return this.phase;
}
public final void setPhase(int phase) {
this.phase = phase;
}
@Override
public boolean isAutoStartup() {
return this.autoStartup;
}
public final void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
@Override
public final int size() {
return this.channels.size();
}
@Override
protected void onInit() throws Exception {
super.onInit();
Assert.notNull(this.getTaskScheduler(), "a task scheduler is required");
}
@Override
public synchronized void start() {
if (!this.running) {
Assert.notNull(this.getTaskScheduler(), "a task scheduler is required");
this.reaperScheduledFuture = this.getTaskScheduler().schedule(this,
new Date(System.currentTimeMillis() + this.reaperDelay));
this.running = true;
}
}
@Override
public synchronized void stop() {
this.running = false;
if (this.reaperScheduledFuture != null) {
this.reaperScheduledFuture.cancel(true);
}
}
@Override
public void stop(Runnable callback) {
this.stop();
callback.run();
}
@Override
public boolean isRunning() {
return this.running;
}
@Override
public Object channelToChannelName(Object channel) {
if (channel != null && channel instanceof MessageChannel) {
String name = this.uuid + DefaultHeaderChannelRegistry.id.incrementAndGet();
channels.put(name, new MessageChannelWrapper((MessageChannel) channel));
if (logger.isDebugEnabled()) {
logger.debug("Registered " + channel + " as " + name);
}
return name;
}
else {
return channel;
}
}
@Override
public MessageChannel channelNameToChannel(String name) {
if (name != null) {
MessageChannelWrapper messageChannelWrapper = this.channels.get(name);
if (logger.isDebugEnabled() && messageChannelWrapper != null) {
logger.debug("Retrieved " + messageChannelWrapper.getChannel() + " with " + name);
}
return messageChannelWrapper == null ? null : messageChannelWrapper.getChannel();
}
return null;
}
/**
* Cancel the scheduled reap task and run immediately; then reschedule.
*/
@Override
public void runReaper() {
synchronized(this) {
this.reaperScheduledFuture.cancel(false);
this.reaperScheduledFuture = null;
}
this.run();
}
@Override
public void run() {
this.reaperScheduledFuture = null;
if (logger.isTraceEnabled()) {
logger.trace("Reaper started; channels size=" + this.channels.size());
}
Iterator<Entry<String, MessageChannelWrapper>> iterator = this.channels.entrySet().iterator();
long threshold = System.currentTimeMillis() - this.reaperDelay;
while (iterator.hasNext()) {
Entry<String, MessageChannelWrapper> entry = iterator.next();
if (entry.getValue().getCreated() < threshold) {
if (logger.isDebugEnabled()) {
logger.debug("Expiring " + entry.getKey() + " (" + entry.getValue().getChannel() + ")");
}
iterator.remove();
}
}
synchronized (this) {
if (this.reaperScheduledFuture == null) {
this.reaperScheduledFuture = this.getTaskScheduler().schedule(this,
new Date(System.currentTimeMillis() + this.reaperDelay));
}
}
if (logger.isTraceEnabled()) {
logger.trace("Reaper completed; channels size=" + this.channels.size());
}
}
private class MessageChannelWrapper {
private final MessageChannel channel;
private final long created;
private MessageChannelWrapper(MessageChannel channel) {
this.channel = channel;
this.created = System.currentTimeMillis();
}
public final long getCreated() {
return created;
}
public final MessageChannel getChannel() {
return channel;
}
}
}

View File

@@ -16,7 +16,10 @@
package org.springframework.integration.config.xml;
import static org.springframework.integration.context.IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -26,8 +29,10 @@ import org.w3c.dom.Node;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedSet;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
@@ -35,9 +40,14 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.integration.channel.DefaultHeaderChannelRegistry;
import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean;
import org.springframework.integration.config.xml.ChannelInitializer.AutoCreateCandidatesCollector;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.context.IntegrationProperties;
import org.springframework.integration.expression.IntegrationEvaluationContextAwareBeanPostProcessor;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -68,15 +78,53 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
private final NamespaceHandlerDelegate delegate = new NamespaceHandlerDelegate();
@Override
public final BeanDefinition parse(Element element, ParserContext parserContext) {
this.verifySchemaVersion(element, parserContext);
this.registerImplicitChannelCreator(parserContext);
this.registerIntegrationEvaluationContext(parserContext);
this.registerIntegrationProperties(parserContext);
this.registerHeaderChannelRegistry(parserContext);
this.registerBuiltInBeans(parserContext);
this.registerDefaultConfiguringBeanFactoryPostProcessorIfNecessary(parserContext);
return this.delegate.parse(element, parserContext);
}
private void registerIntegrationProperties(ParserContext parserContext) {
boolean alreadyRegistered = false;
BeanDefinitionRegistry registry = parserContext.getRegistry();
if (registry instanceof ListableBeanFactory) {
alreadyRegistered = ((ListableBeanFactory) registry)
.containsBean(IntegrationContextUtils.INTEGRATION_PROPERTIES_BEAN_NAME);
}
else {
alreadyRegistered = registry.isBeanNameInUse(IntegrationContextUtils.INTEGRATION_PROPERTIES_BEAN_NAME);
}
if (!alreadyRegistered) {
ResourcePatternResolver resourceResolver =
new PathMatchingResourcePatternResolver(parserContext.getReaderContext().getBeanClassLoader());
try {
Resource[] defaultResources = resourceResolver.getResources("classpath*:META-INF/spring.integration.default.properties");
Resource[] userResources = resourceResolver.getResources("classpath*:META-INF/spring.integration.properties");
List<Resource> resources = new LinkedList<Resource>(Arrays.asList(defaultResources));
resources.addAll(Arrays.asList(userResources));
BeanDefinitionBuilder integrationPropertiesBuilder = BeanDefinitionBuilder
.genericBeanDefinition(PropertiesFactoryBean.class)
.addPropertyValue("locations", resources);
registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_PROPERTIES_BEAN_NAME,
integrationPropertiesBuilder.getBeanDefinition());
}
catch (IOException e) {
parserContext.getReaderContext().warning("Cannot load 'spring.integration.properties' Resources.", null, e);
}
}
}
@Override
public final BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext) {
return this.delegate.decorate(source, definition, parserContext);
}
@@ -98,7 +146,10 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(CHANNEL_INITIALIZER_BEAN_NAME);
}
if (!alreadyRegistered) {
BeanDefinitionBuilder channelDef = BeanDefinitionBuilder.genericBeanDefinition(ChannelInitializer.class);
String channelsAutoCreateExpression = "#{@" +IntegrationContextUtils.INTEGRATION_PROPERTIES_BEAN_NAME +
"['" + IntegrationProperties.CHANNELS_AUTOCREATE + "']}";
BeanDefinitionBuilder channelDef = BeanDefinitionBuilder.genericBeanDefinition(ChannelInitializer.class)
.addPropertyValue("autoCreate", channelsAutoCreateExpression);
BeanDefinitionHolder channelCreatorHolder = new BeanDefinitionHolder(channelDef.getBeanDefinition(), CHANNEL_INITIALIZER_BEAN_NAME);
BeanDefinitionReaderUtils.registerBeanDefinition(channelCreatorHolder, parserContext.getRegistry());
}
@@ -127,10 +178,11 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
// unlike DefaultConfiguringBeanFactoryPostProcessor, we need one of these per registry
// therefore we need to call containsBeanDefinition(..) which does not consider the parent registry
alreadyRegistered = ((ListableBeanFactory) parserContext.getRegistry()).containsBeanDefinition(
INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
}
else {
alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(
IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
}
if (!alreadyRegistered) {
BeanDefinitionBuilder integrationEvaluationContextBuilder = BeanDefinitionBuilder
@@ -171,6 +223,31 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
}
}
String xpathBeanName = "xpath";
alreadyRegistered = false;
if (parserContext.getRegistry() instanceof ListableBeanFactory) {
alreadyRegistered = ((ListableBeanFactory) parserContext.getRegistry()).containsBean(xpathBeanName);
}
else {
alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(xpathBeanName);
}
if (!alreadyRegistered) {
Class<?> xpathClass = null;
try {
xpathClass = ClassUtils.forName(IntegrationNamespaceUtils.BASE_PACKAGE + ".xml.xpath.XPathUtils",
parserContext.getReaderContext().getBeanClassLoader());
}
catch (ClassNotFoundException e) {
logger.debug("SpEL function '#xpath' isn't registered: there is no spring-integration-xml.jar on the classpath.");
}
if (xpathClass != null) {
IntegrationNamespaceUtils.registerSpelFunctionBean(parserContext.getRegistry(), xpathBeanName,
IntegrationNamespaceUtils.BASE_PACKAGE + ".xml.xpath.XPathUtils", "evaluate");
}
}
this.doRegisterBuiltInBeans(parserContext);
}
@@ -195,6 +272,33 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
}
}
/**
* Register a DefaultHeaderChannelRegistry in the given BeanDefinitionRegistry, if necessary.
*/
private void registerHeaderChannelRegistry(ParserContext parserContext) {
boolean alreadyRegistered = false;
if (parserContext.getRegistry() instanceof ListableBeanFactory) {
alreadyRegistered = ((ListableBeanFactory) parserContext.getRegistry())
.containsBean(IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME);
}
else {
alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(
IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME);
}
if (!alreadyRegistered) {
if (logger.isInfoEnabled()) {
logger.info("No bean named '" + IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME +
"' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created.");
}
BeanDefinitionBuilder schedulerBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultHeaderChannelRegistry.class);
BeanDefinitionHolder replyChannelRegistryComponent = new BeanDefinitionHolder(
schedulerBuilder.getBeanDefinition(),
IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME);
BeanDefinitionReaderUtils.registerBeanDefinition(replyChannelRegistryComponent, parserContext.getRegistry());
}
}
protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator decorator) {
this.delegate.doRegisterBeanDefinitionDecorator(elementName, decorator);
}
@@ -225,6 +329,7 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
private class NamespaceHandlerDelegate extends NamespaceHandlerSupport {
@Override
public void init() {
AbstractIntegrationNamespaceHandler.this.init();
}

View File

@@ -37,7 +37,6 @@ import org.springframework.util.xml.DomUtils;
public abstract class AbstractPollingInboundChannelAdapterParser extends AbstractChannelAdapterParser {
@Override
@SuppressWarnings("unchecked")
protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) {
BeanMetadataElement source = this.parseSource(element, parserContext);
if (source == null) {

View File

@@ -29,11 +29,12 @@ import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.expression.DynamicExpression;
import org.springframework.integration.transformer.HeaderEnricher;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.springframework.integration.expression.DynamicExpression;
import org.springframework.integration.transformer.HeaderEnricher;
/**
* Base support class for 'header-enricher' parsers.
@@ -41,6 +42,7 @@ import org.springframework.integration.transformer.HeaderEnricher;
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
* @author Gary Russell
* @since 2.0
*/
public abstract class HeaderEnricherParserSupport extends AbstractTransformerParser {
@@ -49,6 +51,16 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
private final Map<String, Class<?>> elementToTypeMap = new HashMap<String, Class<?>>();
private final static Map<String, String[][]> cannedHeaderElementExpressions = new HashMap<String, String[][]>();
static {
cannedHeaderElementExpressions.put("header-channels-to-string", new String[][] {
{"replyChannel", "@" + IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME
+ ".channelToChannelName(headers.replyChannel)" },
{"errorChannel", "@" + IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME
+ ".channelToChannelName(headers.errorChannel)" },
});
}
@Override
protected final String getTransformerClassName() {
@@ -86,6 +98,8 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
Element headerElement = (Element) node;
String elementName = node.getLocalName();
Class<?> headerType = null;
String expression = null;
String overwrite = headerElement.getAttribute("overwrite");
if ("header".equals(elementName)) {
headerName = headerElement.getAttribute(NAME_ATTRIBUTE);
}
@@ -114,135 +128,157 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
}
}
}
if (headerName != null) {
String value = headerElement.getAttribute("value");
String ref = headerElement.getAttribute(REF_ATTRIBUTE);
String method = headerElement.getAttribute(METHOD_ATTRIBUTE);
String expression = headerElement.getAttribute(EXPRESSION_ATTRIBUTE);
Element beanElement = null;
Element scriptElement = null;
Element expressionElement = null;
List<Element> subElements = DomUtils.getChildElements(headerElement);
if (!subElements.isEmpty()) {
Element subElement = subElements.get(0);
String subElementLocalName = subElement.getLocalName();
if ("bean".equals(subElementLocalName)) {
beanElement = subElement;
}
else if ("script".equals(subElementLocalName)) {
scriptElement = subElement;
}
else if ("expression".equals(subElementLocalName)) {
expressionElement = subElement;
}
if (beanElement == null && scriptElement == null && expressionElement == null) {
parserContext.getReaderContext().error("Only 'bean', 'script' or 'expression' can be defined as a sub-element", element);
if (headerName == null) {
if (cannedHeaderElementExpressions.containsKey(elementName)) {
for (int j = 0; j < cannedHeaderElementExpressions.get(elementName).length; j++) {
headerName = cannedHeaderElementExpressions.get(elementName)[j][0];
expression = cannedHeaderElementExpressions.get(elementName)[j][1];
overwrite = "true";
this.addHeader(element, headers, parserContext, headerName, headerElement, headerType,
expression, overwrite);
}
}
if (StringUtils.hasText(expression) && expressionElement != null) {
parserContext.getReaderContext().error("The 'expression' attribute and sub-element are mutually exclusive", element);
}
boolean isValue = StringUtils.hasText(value);
boolean isRef = StringUtils.hasText(ref);
boolean hasMethod = StringUtils.hasText(method);
boolean isExpression = StringUtils.hasText(expression) || expressionElement != null;
boolean isScript = scriptElement != null;
BeanDefinition innerComponentDefinition = null;
if (beanElement != null) {
innerComponentDefinition = parserContext.getDelegate().parseBeanDefinitionElement(beanElement).getBeanDefinition();
}
else if (isScript) {
innerComponentDefinition = parserContext.getDelegate().parseCustomElement(scriptElement);
}
boolean isCustomBean = innerComponentDefinition != null;
if (hasMethod && isScript) {
parserContext.getReaderContext().error("The 'method' attribute cannot be used when a 'script' sub-element is defined", element);
}
if (!(isValue ^ (isRef ^ (isExpression ^ isCustomBean)))) {
parserContext.getReaderContext().error(
"Exactly one of the 'ref', 'value', 'expression' or inner bean is required.", element);
}
BeanDefinitionBuilder valueProcessorBuilder = null;
if (isValue) {
if (hasMethod) {
parserContext.getReaderContext().error(
"The 'method' attribute cannot be used with the 'value' attribute.", element);
}
Object headerValue = (headerType != null) ?
new TypedStringValue(value, headerType) : value;
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
valueProcessorBuilder.addConstructorArgValue(headerValue);
}
else if (isExpression) {
if (hasMethod) {
parserContext.getReaderContext().error(
"The 'method' attribute cannot be used with the 'expression' attribute.", element);
}
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor");
if (expressionElement != null) {
BeanDefinitionBuilder dynamicExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DynamicExpression.class);
dynamicExpressionBuilder.addConstructorArgValue(expressionElement.getAttribute("key"));
dynamicExpressionBuilder.addConstructorArgReference(expressionElement.getAttribute("source"));
valueProcessorBuilder.addConstructorArgValue(dynamicExpressionBuilder.getBeanDefinition());
}
else {
valueProcessorBuilder.addConstructorArgValue(expression);
}
valueProcessorBuilder.addConstructorArgValue(headerType);
}
else if (isCustomBean) {
if (StringUtils.hasText(headerElement.getAttribute("type"))) {
parserContext.getReaderContext().error(
"The 'type' attribute cannot be used with an inner bean.", element);
}
if (hasMethod || isScript) {
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.MessageProcessingHeaderValueMessageProcessor");
valueProcessorBuilder.addConstructorArgValue(innerComponentDefinition);
if (hasMethod) {
valueProcessorBuilder.addConstructorArgValue(method);
}
}
else {
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
valueProcessorBuilder.addConstructorArgValue(innerComponentDefinition);
}
}
else {
if (StringUtils.hasText(headerElement.getAttribute("type"))) {
parserContext.getReaderContext().error(
"The 'type' attribute cannot be used with the 'ref' attribute.", element);
}
if (hasMethod) {
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.MessageProcessingHeaderValueMessageProcessor");
valueProcessorBuilder.addConstructorArgReference(ref);
valueProcessorBuilder.addConstructorArgValue(method);
}
else {
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
valueProcessorBuilder.addConstructorArgReference(ref);
}
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(valueProcessorBuilder, headerElement, "overwrite");
headers.put(headerName, valueProcessorBuilder.getBeanDefinition());
}
else {
this.addHeader(element, headers, parserContext, headerName, headerElement, headerType, expression,
overwrite);
}
}
}
}
private void addHeader(Element element, ManagedMap<String, Object> headers, ParserContext parserContext,
String headerName, Element headerElement, Class<?> headerType, String expression, String overwrite) {
String value = headerElement.getAttribute("value");
String ref = headerElement.getAttribute(REF_ATTRIBUTE);
String method = headerElement.getAttribute(METHOD_ATTRIBUTE);
if (expression == null) {
expression = headerElement.getAttribute(EXPRESSION_ATTRIBUTE);
}
Element beanElement = null;
Element scriptElement = null;
Element expressionElement = null;
List<Element> subElements = DomUtils.getChildElements(headerElement);
if (!subElements.isEmpty()) {
Element subElement = subElements.get(0);
String subElementLocalName = subElement.getLocalName();
if ("bean".equals(subElementLocalName)) {
beanElement = subElement;
}
else if ("script".equals(subElementLocalName)) {
scriptElement = subElement;
}
else if ("expression".equals(subElementLocalName)) {
expressionElement = subElement;
}
if (beanElement == null && scriptElement == null && expressionElement == null) {
parserContext.getReaderContext().error("Only 'bean', 'script' or 'expression' can be defined as a sub-element", element);
}
}
if (StringUtils.hasText(expression) && expressionElement != null) {
parserContext.getReaderContext().error("The 'expression' attribute and sub-element are mutually exclusive", element);
}
boolean isValue = StringUtils.hasText(value);
boolean isRef = StringUtils.hasText(ref);
boolean hasMethod = StringUtils.hasText(method);
boolean isExpression = StringUtils.hasText(expression) || expressionElement != null;
boolean isScript = scriptElement != null;
BeanDefinition innerComponentDefinition = null;
if (beanElement != null) {
innerComponentDefinition = parserContext.getDelegate().parseBeanDefinitionElement(beanElement).getBeanDefinition();
}
else if (isScript) {
innerComponentDefinition = parserContext.getDelegate().parseCustomElement(scriptElement);
}
boolean isCustomBean = innerComponentDefinition != null;
if (hasMethod && isScript) {
parserContext.getReaderContext().error("The 'method' attribute cannot be used when a 'script' sub-element is defined", element);
}
if (!(isValue ^ (isRef ^ (isExpression ^ isCustomBean)))) {
parserContext.getReaderContext().error(
"Exactly one of the 'ref', 'value', 'expression' or inner bean is required.", element);
}
BeanDefinitionBuilder valueProcessorBuilder = null;
if (isValue) {
if (hasMethod) {
parserContext.getReaderContext().error(
"The 'method' attribute cannot be used with the 'value' attribute.", element);
}
Object headerValue = (headerType != null) ?
new TypedStringValue(value, headerType) : value;
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
valueProcessorBuilder.addConstructorArgValue(headerValue);
}
else if (isExpression) {
if (hasMethod) {
parserContext.getReaderContext().error(
"The 'method' attribute cannot be used with the 'expression' attribute.", element);
}
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor");
if (expressionElement != null) {
BeanDefinitionBuilder dynamicExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DynamicExpression.class);
dynamicExpressionBuilder.addConstructorArgValue(expressionElement.getAttribute("key"));
dynamicExpressionBuilder.addConstructorArgReference(expressionElement.getAttribute("source"));
valueProcessorBuilder.addConstructorArgValue(dynamicExpressionBuilder.getBeanDefinition());
}
else {
valueProcessorBuilder.addConstructorArgValue(expression);
}
valueProcessorBuilder.addConstructorArgValue(headerType);
}
else if (isCustomBean) {
if (StringUtils.hasText(headerElement.getAttribute("type"))) {
parserContext.getReaderContext().error(
"The 'type' attribute cannot be used with an inner bean.", element);
}
if (hasMethod || isScript) {
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.MessageProcessingHeaderValueMessageProcessor");
valueProcessorBuilder.addConstructorArgValue(innerComponentDefinition);
if (hasMethod) {
valueProcessorBuilder.addConstructorArgValue(method);
}
}
else {
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
valueProcessorBuilder.addConstructorArgValue(innerComponentDefinition);
}
}
else {
if (StringUtils.hasText(headerElement.getAttribute("type"))) {
parserContext.getReaderContext().error(
"The 'type' attribute cannot be used with the 'ref' attribute.", element);
}
if (hasMethod) {
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.MessageProcessingHeaderValueMessageProcessor");
valueProcessorBuilder.addConstructorArgReference(ref);
valueProcessorBuilder.addConstructorArgValue(method);
}
else {
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
valueProcessorBuilder.addConstructorArgReference(ref);
}
}
if (StringUtils.hasText(overwrite)) {
valueProcessorBuilder.addPropertyValue("overwrite", overwrite);
}
headers.put(headerName, valueProcessorBuilder.getBeanDefinition());
}
/**
* Subclasses may override this method to provide any additional processing.
*/

View File

@@ -16,6 +16,8 @@
package org.springframework.integration.context;
import java.util.Properties;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@@ -45,6 +47,12 @@ public abstract class IntegrationContextUtils {
public static final String INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME = "integrationEvaluationContext";
public static final String INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME = "integrationHeaderChannelRegistry";
public static final String INTEGRATION_PROPERTIES_BEAN_NAME = "integrationProperties";
private static final Properties EMPTY_PROPERTIES = new Properties();
/**
* Return the {@link MetadataStore} bean whose name is "metadataStore".
* @param beanFactory BeanFactory for lookup, must not be null.
@@ -104,4 +112,24 @@ public abstract class IntegrationContextUtils {
return beanFactory.getBean(beanName, type);
}
/**
* @return the global {@link IntegrationContextUtils#INTEGRATION_PROPERTIES_BEAN_NAME}
* bean from provided {@code #beanFactory}, which represents the merged
* properties values from all 'META-INF/spring.integration.default.properties'
* and 'META-INF/spring.integration.properties'.
* May return {@link IntegrationContextUtils#EMPTY_PROPERTIES} if there is no
* {@link IntegrationContextUtils#INTEGRATION_PROPERTIES_BEAN_NAME} bean within
* provided {@code #beanFactory} or provided {@code #beanFactory} is null.
*/
public static Properties getIntegrationProperties(BeanFactory beanFactory) {
Properties properties = null;
if (beanFactory != null) {
properties = getBeanOfType(beanFactory, INTEGRATION_PROPERTIES_BEAN_NAME, Properties.class);
}
if (properties == null) {
properties = EMPTY_PROPERTIES;
}
return properties;
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.integration.context;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -47,6 +49,7 @@ import org.springframework.util.StringUtils;
* @author Josh Long
* @author Stefan Ferstl
* @author Gary Russell
* @author Artem Bilan
*/
public abstract class IntegrationObjectSupport implements BeanNameAware, NamedComponent,
ApplicationContextAware, BeanFactoryAware, InitializingBean {
@@ -169,6 +172,13 @@ public abstract class IntegrationObjectSupport implements BeanNameAware, NamedCo
return this.applicationContext == null ? null : this.applicationContext.getId();
}
/**
* @see IntegrationContextUtils#getIntegrationProperties
*/
protected Properties getIntegrationProperties() {
return IntegrationContextUtils.getIntegrationProperties(this.beanFactory);
}
@Override
public String toString() {
return (this.beanName != null) ? this.beanName : super.toString();

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2013 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.context;
/**
* Convention Enumeration to represent keys from 'META-INF/spring.integration.properties'.
*
* @author Artem Bilan
* @since 3.0
*/
public interface IntegrationProperties {
String LATE_REPLY_LOGGING_LEVEL = "messagingTemplate.lateReply.logging.level";
String CHANNELS_AUTOCREATE = "channels.autoCreate";
}

View File

@@ -23,10 +23,13 @@ import java.util.Set;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.util.Assert;
/**
* <p>
* An immutable {@link AbstractMap} implementation that wraps a Map<String, Expression>
* An immutable {@link AbstractMap} implementation that wraps a {@code Map<String, Object>},
* where values must be instances of {@link String} or {@link Expression},
* and evaluates an {@code expression} for the provided {@code key} from the underlying
* {@code original} Map.
* </p>
@@ -38,16 +41,18 @@ import org.springframework.expression.Expression;
* <p>
* A {@link ExpressionEvalMapBuilder} must be used to instantiate this class
* via its {@link #from(Map)} method:
* <pre class="code">
* {@code
* ExpressionEvalMap evalMap = ExpressionEvalMap
* .from(expressions)
.usingCallback(new EvaluationCallback() {
Object evaluate(Expression expression) {
// return some expression evaluation
}
})
.build();
* }
*ExpressionEvalMap evalMap = ExpressionEvalMap
* .from(expressions)
* .usingCallback(new EvaluationCallback() {
* Object evaluate(Expression expression) {
* // return some expression evaluation
* }
* })
* .build();
*}
* </pre>
* </p>
* <p>
* Thread-safety depends on the original underlying Map.
@@ -68,11 +73,11 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
};
private final Map<String, Expression> original;
private final Map<String, ?> original;
private final EvaluationCallback evaluationCallback;
private ExpressionEvalMap(Map<String, Expression> original, EvaluationCallback evaluationCallback) {
private ExpressionEvalMap(Map<String, ?> original, EvaluationCallback evaluationCallback) {
this.original = original;
this.evaluationCallback = evaluationCallback;
}
@@ -83,8 +88,20 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
*/
@Override
public Object get(Object key) {
Expression expression = original.get(key);
if (expression != null) {
Object value = original.get(key);
if (value != null) {
Expression expression;
if (value instanceof Expression) {
expression = (Expression) value;
}
else if (value instanceof String) {
expression = new LiteralExpression((String) value);
}
else {
throw new IllegalArgumentException("Values must be "
+ "'java.lang.String' or 'org.springframework.expression.Expression'; the value type for key "
+ key + " is : " + value.getClass());
}
return this.evaluationCallback.evaluate(expression);
}
return null;
@@ -136,7 +153,7 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
}
@Override
public void putAll(Map<? extends String, ? extends Object> m) {
public void putAll(Map<? extends String, ?> m) {
throw new UnsupportedOperationException();
}
@@ -155,7 +172,13 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
throw new UnsupportedOperationException();
}
public static ExpressionEvalMapBuilder from(Map<String, Expression> expressions) {
@Override
public String toString() {
return this.original.toString();
}
public static ExpressionEvalMapBuilder from(Map<String, ?> expressions) {
Assert.notNull(expressions, "'expressions' must not be null.");
return new ExpressionEvalMapBuilder(expressions);
}
@@ -205,7 +228,7 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
*/
public static final class ExpressionEvalMapBuilder {
private final Map<String, Expression> expressions;
private final Map<String, ?> expressions;
private EvaluationCallback evaluationCallback;
@@ -219,7 +242,7 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
private final ExpressionEvalMapFinalBuilder finalBuilder = new ExpressionEvalMapFinalBuilderImpl();
private ExpressionEvalMapBuilder(Map<String, Expression> expressions) {
private ExpressionEvalMapBuilder(Map<String, ?> expressions) {
this.expressions = expressions;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2013 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,7 +28,6 @@ import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.converter.SimpleMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.SubscribableChannel;
@@ -43,6 +42,7 @@ import org.springframework.util.Assert;
* well as the timeout values for sending and receiving Messages.
*
* @author Mark Fisher
* @author Gary Russell
*/
public abstract class MessagingGatewaySupport extends AbstractEndpoint implements TrackableComponent {
@@ -153,6 +153,7 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint implement
* Specify whether this gateway should be tracked in the Message History
* of Messages that originate from its send or sendAndReceive operations.
*/
@Override
public void setShouldTrack(boolean shouldTrack) {
this.historyWritingPostProcessor.setShouldTrack(shouldTrack);
}
@@ -283,7 +284,11 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint implement
return;
}
AbstractEndpoint correlator = null;
MessageHandler handler = new BridgeHandler();
BridgeHandler handler = new BridgeHandler();
if (this.getBeanFactory() != null) {
handler.setBeanFactory(this.getBeanFactory());
}
handler.afterPropertiesSet();
if (this.replyChannel instanceof SubscribableChannel) {
correlator = new EventDrivenConsumer(
(SubscribableChannel) this.replyChannel, handler);
@@ -320,6 +325,7 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint implement
private static class DefaultRequestMapper implements InboundMessageMapper<Object> {
@Override
public Message<?> toMessage(Object object) throws Exception {
if (object instanceof Message<?>) {
return (Message<?>) object;

View File

@@ -28,6 +28,7 @@ import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
@@ -62,6 +63,7 @@ public class PropertiesPersistingMetadataStore implements MetadataStore, Initial
this.baseDirectory = baseDirectory;
}
@Override
public void afterPropertiesSet() throws Exception {
File baseDir = new File(baseDirectory);
baseDir.mkdirs();
@@ -78,20 +80,22 @@ public class PropertiesPersistingMetadataStore implements MetadataStore, Initial
this.loadMetadata();
}
@Override
public void put(String key, String value) {
this.metadata.setProperty(key, value);
}
@Override
public String get(String key) {
return this.metadata.getProperty(key);
}
@Override
@SuppressWarnings("uchecked")
public String remove(String key) {
return (String) this.metadata.remove(key);
}
@Override
public void destroy() throws Exception {
this.saveMetadata();
}
@@ -100,12 +104,12 @@ public class PropertiesPersistingMetadataStore implements MetadataStore, Initial
OutputStream outputStream = null;
try {
outputStream = new BufferedOutputStream(new FileOutputStream(this.file));
this.persister.store(this.metadata, outputStream, "Last feed entry");
this.persister.store(this.metadata, outputStream, "Last entry");
}
catch (IOException e) {
// not fatal for the functionality of the component
logger.warn("Failed to persist feed entry. This may result in a duplicate "
+ "feed entry after this component is restarted.", e);
logger.warn("Failed to persist entry. This may result in a duplicate "
+ "entry after this component is restarted.", e);
}
finally {
try {
@@ -128,8 +132,8 @@ public class PropertiesPersistingMetadataStore implements MetadataStore, Initial
}
catch (Exception e) {
// not fatal for the functionality of the component
logger.warn("Failed to load feed entry from the persistent store. This may result in a duplicate " +
"feed entry after this component is restarted", e);
logger.warn("Failed to load entry from the persistent store. This may result in a duplicate " +
"entry after this component is restarted", e);
}
finally {
try {

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2002-2010 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.support.channel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.core.DestinationResolutionException;
import org.springframework.messaging.core.DestinationResolver;
import org.springframework.util.Assert;
/**
* {@link ChannelResolver} implementation based on a Spring {@link BeanFactory}.
*
* <p>Will lookup Spring managed beans identified by bean name,
* expecting them to be of type {@link MessageChannel}.
*
* @author Mark Fisher
* @see org.springframework.beans.factory.BeanFactory
*/
public class BeanFactoryChannelResolver implements DestinationResolver<MessageChannel>, BeanFactoryAware {
private final static Log logger = LogFactory.getLog(BeanFactoryChannelResolver.class);
private volatile BeanFactory beanFactory;
private volatile HeaderChannelRegistry replyChannelRegistry;
/**
* Create a new instance of the {@link BeanFactoryChannelResolver} class.
* <p>The BeanFactory to access must be set via <code>setBeanFactory</code>.
* This will happen automatically if this resolver is defined within an
* ApplicationContext thereby receiving the callback upon initialization.
* @see #setBeanFactory
*/
public BeanFactoryChannelResolver() {
}
/**
* Create a new instance of the {@link BeanFactoryChannelResolver} class.
* <p>Use of this constructor is redundant if this object is being created
* by a Spring IoC container as the supplied {@link BeanFactory} will be
* replaced by the {@link BeanFactory} that creates it (c.f. the
* {@link BeanFactoryAware} contract). So only use this constructor if you
* are instantiating this object explicitly rather than defining a bean.
*
* @param beanFactory the bean factory to be used to lookup {@link MessageChannel}s.
*/
public BeanFactoryChannelResolver(BeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.lookupHeaderChannelRegistry(beanFactory);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
this.lookupHeaderChannelRegistry(beanFactory);
}
private void lookupHeaderChannelRegistry(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
try {
this.replyChannelRegistry = beanFactory.getBean(
IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME,
HeaderChannelRegistry.class);
}
catch (Exception e) {
logger.warn("No HeaderChannelRegistry found", e);
}
}
@Override
public MessageChannel resolveDestination(String name) {
Assert.state(this.beanFactory != null, "BeanFactory is required");
try {
return this.beanFactory.getBean(name, MessageChannel.class);
}
catch (BeansException e) {
if (this.replyChannelRegistry != null) {
MessageChannel channel = this.replyChannelRegistry.channelNameToChannel(name);
if (channel != null) {
return channel;
}
}
throw new DestinationResolutionException(
"failed to look up MessageChannel bean with name '" + name + "'", e);
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2013 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.support.channel;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.messaging.MessageChannel;
/**
* Implementations convert a channel to a name, retaining a reference to the channel keyed by the name.
* Allows a downstream {@link BeanFactoryChannelResolver} to find the channel by name in
* the event that the flow serialized the message at some point.
*
* @author Gary Russell
* @since 3.0
*
*/
public interface HeaderChannelRegistry {
/**
* Converts the channel to a name (String). If the channel is not a
* {@link MessageChannel}, it is returned unchanged.
*
* @param channel The channel.
* @return The channel name, or the channel if it is not a MessageChannel.
*/
public abstract Object channelToChannelName(Object channel);
/**
* Converts the channel name back to a {@link MessageChannel} (if it is
* registered).
* @param name The name of the channel.
* @return The channel, or null if there is no channel registered with the name.
*/
public abstract MessageChannel channelNameToChannel(String name);
/**
* @return the current size of the registry
*/
@ManagedAttribute
public abstract int size();
/**
* Cancel the scheduled reap task and run immediately; then reschedule.
*/
@ManagedOperation(description = "Cancel the scheduled reap task and run immediately; then reschedule.")
public abstract void runReaper();
}

View File

@@ -214,6 +214,10 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
this.gateway.setReplyChannel(replyChannel);
}
if (this.getBeanFactory() != null) {
this.gateway.setBeanFactory(this.getBeanFactory());
}
this.gateway.afterPropertiesSet();
}
@@ -294,6 +298,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
* Lifecycle implementation. If no requestChannel is defined, this method
* has no effect as in that case no Gateway is initialized.
*/
@Override
public void start() {
if (this.gateway != null) {
this.gateway.start();
@@ -304,6 +309,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
* Lifecycle implementation. If no requestChannel is defined, this method
* has no effect as in that case no Gateway is initialized.
*/
@Override
public void stop() {
if (this.gateway != null) {
this.gateway.stop();
@@ -314,6 +320,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
* Lifecycle implementation. If no requestChannel is defined, this method
* will return always return true as no Gateway is initialized.
*/
@Override
public boolean isRunning() {
if (this.gateway != null) {
return this.gateway.isRunning();

View File

@@ -28,6 +28,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -49,15 +50,16 @@ import org.springframework.expression.Expression;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.messaging.Message;
import org.springframework.integration.MessageHandlingException;
import org.springframework.messaging.MessagingException;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.annotation.Headers;
import org.springframework.integration.annotation.Payload;
import org.springframework.integration.annotation.Payloads;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.util.ReflectionUtils.MethodFilter;
@@ -76,21 +78,31 @@ import org.springframework.util.StringUtils;
* @author Gunnar Hillert
* @author Soby Chacko
* @author Gary Russell
* @author Artem Bilan
*
* @since 2.0
*/
public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator {
private static final String CANDIDATE_METHODS = "CANDIDATE_METHODS";
private static final String CANDIDATE_MESSAGE_METHODS = "CANDIDATE_MESSAGE_METHODS";
private final Log logger = LogFactory.getLog(this.getClass());
private final Object targetObject;
private volatile String displayString;
private volatile boolean requiresReply;
private final Map<Class<?>, HandlerMethod> handlerMethods;
private final Map<Class<?>, HandlerMethod> handlerMessageMethods;
private final LinkedList<Map<Class<?>, HandlerMethod>> handlerMethodsList;
private final HandlerMethod handlerMethod;
private final Class<?> expectedType;
private final boolean canProcessMessageList;
@@ -154,11 +166,12 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
Assert.isTrue(method.getReturnType() != Void.class && method.getReturnType() != Void.TYPE,
"method must have a return type");
}
HandlerMethod handlerMethod = new HandlerMethod(method, canProcessMessageList);
Assert.notNull(targetObject, "targetObject must not be null");
this.targetObject = targetObject;
this.handlerMethods = Collections.<Class<?>, HandlerMethod> singletonMap(handlerMethod.getTargetParameterType()
.getObjectType(), handlerMethod);
this.handlerMethod = new HandlerMethod(method, canProcessMessageList);
this.handlerMethods = null;
this.handlerMessageMethods = null;
this.handlerMethodsList = null;
this.prepareEvaluationContext(this.getEvaluationContext(false), method, annotationType);
this.setDisplayString(targetObject, method);
}
@@ -170,7 +183,32 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
this.expectedType = expectedType;
this.targetObject = targetObject;
this.requiresReply = expectedType != null;
this.handlerMethods = this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, requiresReply);
Map<String, Map<Class<?>, HandlerMethod>> handlerMethodsForTarget =
this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, requiresReply);
Map<Class<?>, HandlerMethod> handlerMethods = handlerMethodsForTarget.get(CANDIDATE_METHODS);
Map<Class<?>, HandlerMethod> handlerMessageMethods = handlerMethodsForTarget.get(CANDIDATE_MESSAGE_METHODS);
if ((handlerMethods.size() == 1 && handlerMessageMethods.isEmpty()) ||
(handlerMessageMethods.size() == 1 && handlerMethods.isEmpty())) {
if (handlerMethods.size() == 1) {
this.handlerMethod = handlerMethods.values().iterator().next();
}
else {
this.handlerMethod = handlerMessageMethods.values().iterator().next();
}
this.handlerMethods = null;
this.handlerMessageMethods = null;
this.handlerMethodsList = null;
}
else {
this.handlerMethod = null;
this.handlerMethods = handlerMethods;
this.handlerMessageMethods = handlerMessageMethods;
this.handlerMethodsList = new LinkedList<Map<Class<?>, HandlerMethod>>();
//TODO Consider to use global option to determine a precedence of methods
this.handlerMethodsList.add(this.handlerMethods);
this.handlerMethodsList.add(this.handlerMessageMethods);
}
this.prepareEvaluationContext(this.getEvaluationContext(false), methodName, annotationType);
this.setDisplayString(targetObject, methodName);
}
@@ -181,7 +219,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
sb.append("." + ((Method) targetMethod).getName());
}
else if (targetMethod instanceof String) {
sb.append("." + (String) targetMethod);
sb.append("." + targetMethod);
}
this.displayString = sb.toString() + "]";
}
@@ -192,7 +230,8 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
if (method instanceof Method) {
context.registerMethodFilter(targetType, new FixedMethodFilter((Method) method));
if (expectedType != null) {
Assert.state(context.getTypeConverter().canConvert(TypeDescriptor.valueOf(((Method) method).getReturnType()), TypeDescriptor.valueOf(expectedType)),
Assert.state(context.getTypeConverter().canConvert(TypeDescriptor.valueOf(((Method) method).getReturnType()),
TypeDescriptor.valueOf(expectedType)),
"Cannot convert to expected type (" + expectedType + ") from " + method);
}
}
@@ -220,64 +259,48 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
}
private T processInternal(ParametersWrapper parameters) throws Exception {
Throwable evaluationException = null;
List<HandlerMethod> candidates = this.findHandlerMethodsForParameters(parameters);
Assert.state(!candidates.isEmpty(), "No candidate methods found for messages.");
for (HandlerMethod candidate : candidates) {
try {
Expression expression = candidate.getExpression();
Class<?> expectedType = this.expectedType != null ? this.expectedType : candidate.method.getReturnType();
@SuppressWarnings("unchecked")
T result = (T) this.evaluateExpression(expression, parameters, expectedType);
if (this.requiresReply) {
Assert.notNull(result,
"Expression evaluation result was null, but this processor requires a reply.");
}
return result;
}
// keep the first exception
catch (EvaluationException e) {
if (evaluationException == null) {
evaluationException = e.getCause();
}
if (evaluationException == null) {
evaluationException = e;
}
}
catch (MessageHandlingException e) {
if (evaluationException == null) {
evaluationException = e.getCause();
}
if (evaluationException == null) {
evaluationException = e;
}
}
catch (Exception e) {
if (evaluationException == null) {
evaluationException = e;
}
HandlerMethod candidate = this.findHandlerMethodForParameters(parameters);
Assert.notNull(candidate, "No candidate methods found for messages.");
Expression expression = candidate.getExpression();
Class<?> expectedType = this.expectedType != null ? this.expectedType : candidate.method.getReturnType();
try {
@SuppressWarnings("unchecked")
T result = (T) this.evaluateExpression(expression, parameters, expectedType);
if (this.requiresReply) {
Assert.notNull(result,
"Expression evaluation result was null, but this processor requires a reply.");
}
return result;
}
if (evaluationException instanceof Exception) {
throw (Exception) evaluationException;
}
else if (evaluationException instanceof Error) {
throw (Error) evaluationException;
}
else {
throw new IllegalStateException("Cannot process message", evaluationException);
catch (Exception e) {
Throwable evaluationException = e;
if ((e instanceof EvaluationException || e instanceof MessageHandlingException) && e.getCause() != null) {
evaluationException = e.getCause();
}
if (evaluationException instanceof Exception) {
throw (Exception) evaluationException;
}
else {
throw new IllegalStateException("Cannot process message", evaluationException);
}
}
}
private Map<Class<?>, HandlerMethod> findHandlerMethodsForTarget(final Object targetObject,
private Map<String, Map<Class<?>, HandlerMethod>> findHandlerMethodsForTarget(final Object targetObject,
final Class<? extends Annotation> annotationType, final String methodName, final boolean requiresReply) {
Map<String, Map<Class<?>, HandlerMethod>> handlerMethods = new HashMap<String, Map<Class<?>, HandlerMethod>>();
final Map<Class<?>, HandlerMethod> candidateMethods = new HashMap<Class<?>, HandlerMethod>();
final Map<Class<?>, HandlerMethod> candidateMessageMethods = new HashMap<Class<?>, HandlerMethod>();
final Map<Class<?>, HandlerMethod> fallbackMethods = new HashMap<Class<?>, HandlerMethod>();
final Map<Class<?>, HandlerMethod> fallbackMessageMethods = new HashMap<Class<?>, HandlerMethod>();
final AtomicReference<Class<?>> ambiguousFallbackType = new AtomicReference<Class<?>>();
final AtomicReference<Class<?>> ambiguousFallbackMessageGenericType = new AtomicReference<Class<?>>();
final Class<?> targetClass = this.getTargetClass(targetObject);
MethodFilter methodFilter = new UniqueMethodFilter(targetClass);
ReflectionUtils.doWithMethods(targetClass, new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
boolean matchesAnnotation = false;
if (method.isBridge()) {
@@ -311,37 +334,75 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
}
return;
}
Class<?> targetParameterType = handlerMethod.getTargetParameterType().getObjectType();
Class<?> targetParameterType = handlerMethod.getTargetParameterType();
if (matchesAnnotation || annotationType == null) {
Assert.isTrue(!candidateMethods.containsKey(targetParameterType),
"Found more than one method match for type [" + targetParameterType + "]");
candidateMethods.put(targetParameterType, handlerMethod);
if (handlerMethod.isMessageMethod()) {
if (candidateMessageMethods.containsKey(targetParameterType)) {
throw new IllegalArgumentException("Found more than one method match for type " +
"[Message<" + targetParameterType + ">]");
}
candidateMessageMethods.put(targetParameterType, handlerMethod);
}
else {
if (candidateMethods.containsKey(targetParameterType)) {
String exceptionMessage = "Found more than one method match for ";
if (Void.class.equals(targetParameterType)) {
exceptionMessage += "empty parameter for 'payload'";
}
else {
exceptionMessage += "type [" + targetParameterType + "]";
}
throw new IllegalArgumentException(exceptionMessage);
}
candidateMethods.put(targetParameterType, handlerMethod);
}
}
else {
if (fallbackMethods.containsKey(targetParameterType)) {
// we need to check for duplicate type matches,
// but only if we end up falling back
// and we'll only keep track of the first one
ambiguousFallbackType.compareAndSet(null, targetParameterType);
if (handlerMethod.isMessageMethod()) {
if (fallbackMessageMethods.containsKey(targetParameterType)) {
// we need to check for duplicate type matches,
// but only if we end up falling back
// and we'll only keep track of the first one
ambiguousFallbackMessageGenericType.compareAndSet(null, targetParameterType);
}
fallbackMessageMethods.put(targetParameterType, handlerMethod);
}
else {
if (fallbackMethods.containsKey(targetParameterType)) {
// we need to check for duplicate type matches,
// but only if we end up falling back
// and we'll only keep track of the first one
ambiguousFallbackType.compareAndSet(null, targetParameterType);
}
fallbackMethods.put(targetParameterType, handlerMethod);
}
fallbackMethods.put(targetParameterType, handlerMethod);
}
}
}, methodFilter);
if (!candidateMethods.isEmpty()) {
return candidateMethods;
if (!candidateMethods.isEmpty() || !candidateMessageMethods.isEmpty()) {
handlerMethods.put(CANDIDATE_METHODS, candidateMethods);
handlerMethods.put(CANDIDATE_MESSAGE_METHODS, candidateMessageMethods);
return handlerMethods;
}
if ((fallbackMethods.isEmpty() || ambiguousFallbackType.get() != null) && ServiceActivator.class.equals(annotationType)) {
// a Service Activator can fallback to either MessageHandler.handleMessage(m) or RequestReplyExchanger.exchange(m)
if ((ambiguousFallbackType.get() != null
|| ambiguousFallbackMessageGenericType.get() != null)
&& ServiceActivator.class.equals(annotationType)) {
/*
* When there are ambiguous fallback methods,
* a Service Activator can finally fallback to RequestReplyExchanger.exchange(m).
* Ambiguous means > 1 method that takes the same payload type, or > 1 method
* that takes a Message with the same generic type.
*/
List<Method> frameworkMethods = new ArrayList<Method>();
Class<?>[] allInterfaces = org.springframework.util.ClassUtils.getAllInterfacesForClass(targetClass);
for (Class<?> iface : allInterfaces) {
try {
if ("org.springframework.integration.gateway.RequestReplyExchanger".equals(iface.getName())) {
frameworkMethods.add(targetClass.getMethod("exchange", Message.class));
}
else if ("org.springframework.messaging.MessageHandler".equals(iface.getName()) && !requiresReply) {
frameworkMethods.add(targetClass.getMethod("handleMessage", Message.class));
if (logger.isDebugEnabled()) {
logger.debug(targetObject.getClass() + ": Ambiguous fallback methods; using RequestReplyExchanger.exchange()");
}
}
}
catch (Exception e) {
@@ -350,14 +411,30 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
}
if (frameworkMethods.size() == 1) {
HandlerMethod handlerMethod = new HandlerMethod(frameworkMethods.get(0), canProcessMessageList);
return Collections.<Class<?>, HandlerMethod>singletonMap(Object.class, handlerMethod);
handlerMethods.put(CANDIDATE_METHODS, Collections.<Class<?>, HandlerMethod>singletonMap(Object.class, handlerMethod));
handlerMethods.put(CANDIDATE_MESSAGE_METHODS, candidateMessageMethods);
return handlerMethods;
}
}
Assert.notEmpty(fallbackMethods, "Target object of type [" + this.targetObject.getClass()
+ "] has no eligible methods for handling Messages.");
try {
Assert.state(!fallbackMethods.isEmpty() || !fallbackMessageMethods.isEmpty(),
"Target object of type [" + this.targetObject.getClass() + "] has no eligible methods for handling Messages.");
}
catch (Exception e) {
//TODO backward compatibility
throw new IllegalArgumentException(e.getMessage());
}
Assert.isNull(ambiguousFallbackType.get(), "Found ambiguous parameter type [" + ambiguousFallbackType
+ "] for method match: " + fallbackMethods.values());
return fallbackMethods;
Assert.isNull(ambiguousFallbackMessageGenericType.get(),
"Found ambiguous parameter type [" + ambiguousFallbackMessageGenericType + "] for method match: "
+ fallbackMethods.values());
handlerMethods.put(CANDIDATE_METHODS, fallbackMethods);
handlerMethods.put(CANDIDATE_MESSAGE_METHODS, fallbackMessageMethods);
return handlerMethods;
}
private Class<?> getTargetClass(Object targetObject) {
@@ -388,22 +465,40 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
return targetClass;
}
private List<HandlerMethod> findHandlerMethodsForParameters(ParametersWrapper parameters) {
private HandlerMethod findHandlerMethodForParameters(ParametersWrapper parameters) {
if (this.handlerMethod != null) {
return this.handlerMethod;
}
final Class<?> payloadType = parameters.getFirstParameterType();
HandlerMethod closestMatch = this.findClosestMatch(payloadType);
if (closestMatch != null) {
return Collections.singletonList(closestMatch);
return closestMatch;
}
return new ArrayList<HandlerMethod>(this.handlerMethods.values());
if (Iterable.class.isAssignableFrom(payloadType) && this.handlerMethods.containsKey(Iterator.class)) {
return this.handlerMethods.get(Iterator.class);
}
else {
return this.handlerMethods.get(Void.class);
}
}
private HandlerMethod findClosestMatch(Class<?> payloadType) {
Set<Class<?>> candidates = this.handlerMethods.keySet();
Class<?> match = null;
if (candidates != null && !candidates.isEmpty()) {
match = ClassUtils.findClosestMatch(payloadType, candidates, true);
for (Map<Class<?>, HandlerMethod> handlerMethods : handlerMethodsList) {
Set<Class<?>> candidates = handlerMethods.keySet();
Class<?> match = null;
if (!CollectionUtils.isEmpty(candidates)) {
match = ClassUtils.findClosestMatch(payloadType, candidates, true);
}
if (match != null) {
return handlerMethods.get(match);
}
}
return (match != null) ? this.handlerMethods.get(match) : null;
return null;
}
private static boolean isMethodDefinedOnObjectClass(Method method) {
@@ -446,10 +541,13 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
private final Expression expression;
private volatile TypeDescriptor targetParameterType;
private final boolean canProcessMessageList;
private volatile TypeDescriptor targetParameterTypeDescriptor;
private volatile Class<?> targetParameterType = Void.class;
private volatile boolean messageMethod;
HandlerMethod(Method method, boolean canProcessMessageList) {
this.method = method;
@@ -462,10 +560,14 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
return this.expression;
}
TypeDescriptor getTargetParameterType() {
Class<?> getTargetParameterType() {
return this.targetParameterType;
}
private boolean isMessageMethod() {
return messageMethod;
}
@Override
public String toString() {
return this.method.toString();
@@ -476,13 +578,12 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
Class<?>[] parameterTypes = method.getParameterTypes();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
boolean hasUnqualifiedMapParameter = false;
TypeDescriptor defaultParameterTypeDescriptor = TypeDescriptor.valueOf(List.class);
for (int i = 0; i < parameterTypes.length; i++) {
if (i != 0) {
sb.append(", ");
}
TypeDescriptor parameterTypeDescriptor = new TypeDescriptor(new MethodParameter(method, i));
defaultParameterTypeDescriptor = parameterTypeDescriptor;
MethodParameter methodParameter = new MethodParameter(method, i);
TypeDescriptor parameterTypeDescriptor = new TypeDescriptor(methodParameter);
Class<?> parameterType = parameterTypeDescriptor.getObjectType();
Annotation mappingAnnotation = findMappingAnnotation(parameterAnnotations[i]);
if (mappingAnnotation != null) {
@@ -494,7 +595,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
sb.append("." + qualifierExpression);
}
if (!StringUtils.hasText(qualifierExpression)) {
this.setExclusiveTargetParameterType(parameterTypeDescriptor);
this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
}
if (annotationType.equals(Payloads.class)) {
@@ -505,7 +606,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
}
sb.append("]");
if (!StringUtils.hasText(qualifierExpression)) {
this.setExclusiveTargetParameterType(parameterTypeDescriptor);
this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
}
else if (annotationType.equals(Headers.class)) {
@@ -515,17 +616,18 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
}
else if (annotationType.equals(Header.class)) {
Header headerAnnotation = (Header) mappingAnnotation;
sb.append(this.determineHeaderExpression(headerAnnotation, new MethodParameter(method, i)));
sb.append(this.determineHeaderExpression(headerAnnotation, methodParameter));
}
}
else if (parameterTypeDescriptor.isAssignableTo(messageTypeDescriptor)) {
this.messageMethod = true;
sb.append("message");
this.setExclusiveTargetParameterType(parameterTypeDescriptor);
this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
else if ((parameterTypeDescriptor.isAssignableTo(messageListTypeDescriptor) || parameterTypeDescriptor
.isAssignableTo(messageArrayTypeDescriptor))) {
sb.append("messages");
this.setExclusiveTargetParameterType(parameterTypeDescriptor);
this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
else if (Collection.class.isAssignableFrom(parameterType) || parameterType.isArray()) {
if (canProcessMessageList) {
@@ -534,11 +636,11 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
else {
sb.append("payload");
}
this.setExclusiveTargetParameterType(parameterTypeDescriptor);
this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
else if (Iterator.class.isAssignableFrom(parameterType)) {
if (canProcessMessageList) {
Type type = method.getGenericParameterTypes()[0];
Type type = method.getGenericParameterTypes()[i];
Type parameterizedType = null;
if (type instanceof ParameterizedType){
parameterizedType = ((ParameterizedType)type).getActualTypeArguments()[0];
@@ -546,7 +648,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
parameterizedType = ((ParameterizedType) parameterizedType).getRawType();
}
}
if (parameterizedType != null && Message.class.isAssignableFrom((Class<?>)parameterizedType)){
if (parameterizedType != null && Message.class.isAssignableFrom((Class<?>) parameterizedType)){
sb.append("messages.iterator()");
}
else {
@@ -556,7 +658,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
else {
sb.append("payload.iterator()");
}
this.setExclusiveTargetParameterType(parameterTypeDescriptor);
this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
else if (Map.class.isAssignableFrom(parameterType)) {
if (Properties.class.isAssignableFrom(parameterType)) {
@@ -573,19 +675,19 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
}
else {
sb.append("payload");
this.setExclusiveTargetParameterType(parameterTypeDescriptor);
this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
}
if (hasUnqualifiedMapParameter) {
if (targetParameterType != null && Map.class.isAssignableFrom(this.targetParameterType.getObjectType())) {
if (targetParameterType != null && Map.class.isAssignableFrom(this.targetParameterType)) {
throw new IllegalArgumentException(
"Unable to determine payload matching parameter due to ambiguous Map typed parameters. "
+ "Consider adding the @Payload and or @Headers annotations as appropriate.");
}
}
sb.append(")");
if (this.targetParameterType == null) {
this.targetParameterType = defaultParameterTypeDescriptor;
if (this.targetParameterTypeDescriptor == null) {
this.targetParameterTypeDescriptor = TypeDescriptor.valueOf(Void.class);
}
return EXPRESSION_PARSER.parseExpression(sb.toString());
}
@@ -638,15 +740,22 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
return headerRetrievalExpression + " != null ? " + fullHeaderExpression + " : " + fallbackExpression;
}
private synchronized void setExclusiveTargetParameterType(TypeDescriptor targetParameterType) {
Assert.isNull(this.targetParameterType, "Found more than one parameter type candidate: ["
+ this.targetParameterType + "] and [" + targetParameterType + "]");
this.targetParameterType = targetParameterType;
private synchronized void setExclusiveTargetParameterType(TypeDescriptor targetParameterType, MethodParameter methodParameter) {
Assert.isNull(this.targetParameterTypeDescriptor, "Found more than one parameter type candidate: ["
+ this.targetParameterTypeDescriptor + "] and [" + targetParameterType + "]");
this.targetParameterTypeDescriptor = targetParameterType;
if (Message.class.isAssignableFrom(targetParameterType.getObjectType())) {
methodParameter.increaseNestingLevel();
this.targetParameterType = methodParameter.getNestedParameterType();
methodParameter.decreaseNestingLevel();
}
else {
this.targetParameterType = targetParameterType.getObjectType();
}
}
}
@SuppressWarnings("unused")
private static class ParametersWrapper {
public class ParametersWrapper {
private final Object payload;
@@ -692,7 +801,7 @@ public class MessagingMethodInvokerHelper<T> extends AbstractExpressionEvaluator
if (payload != null) {
return payload.getClass();
}
return Collection.class;
return this.messages.getClass();
}
}

View File

@@ -0,0 +1,2 @@
channels.autoCreate=true
messagingTemplate.lateReply.logging.level=warn

View File

@@ -1853,6 +1853,15 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="header-channels-to-string">
<xsd:annotation>
<xsd:documentation>
Converts the 'replyChannel' and 'errorChannel' headers to a String after registering it in the HeaderChannelRegistry.
Use this when a message is serialized for any reason. No changes are made
if the header does not exist, or if the header does not currently reference a MessageChannel.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="error-channel" type="referenceOrValueHeaderType">
<xsd:annotation>
<xsd:documentation>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -16,9 +16,13 @@
package org.springframework.integration.aggregator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -30,6 +34,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
@@ -57,6 +62,7 @@ public class AbstractCorrelatingMessageHandlerTests {
AbstractCorrelatingMessageHandler handler = new AbstractCorrelatingMessageHandler(
new MessageGroupProcessor() {
@Override
public Object processMessageGroup(MessageGroup group) {
return group;
}
@@ -73,6 +79,7 @@ public class AbstractCorrelatingMessageHandlerTests {
*/
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
waitReapStartLatch.await(10, TimeUnit.SECONDS);
@@ -98,6 +105,7 @@ public class AbstractCorrelatingMessageHandlerTests {
/*
* Executes when group 'bar' completes normally
*/
@Override
public boolean send(Message<?> message, long timeout) {
outputMessages.add(message);
// wake reaper
@@ -115,12 +123,14 @@ public class AbstractCorrelatingMessageHandlerTests {
return true;
}
@Override
public boolean send(Message<?> message) {
return this.send(message, 0);
}
});
handler.setReleaseStrategy(new ReleaseStrategy() {
@Override
public boolean canRelease(MessageGroup group) {
return group.size() == 2;
}
@@ -162,6 +172,7 @@ public class AbstractCorrelatingMessageHandlerTests {
AggregatingMessageHandler handler = new AggregatingMessageHandler(
new MessageGroupProcessor() {
@Override
public Object processMessageGroup(MessageGroup group) {
return group;
}
@@ -174,17 +185,20 @@ public class AbstractCorrelatingMessageHandlerTests {
/*
* Executes when group 'bar' completes normally
*/
@Override
public boolean send(Message<?> message, long timeout) {
outputMessages.add(message);
return true;
}
@Override
public boolean send(Message<?> message) {
return this.send(message, 0);
}
});
handler.setReleaseStrategy(new ReleaseStrategy() {
@Override
public boolean canRelease(MessageGroup group) {
return group.size() == 1;
}
@@ -208,6 +222,7 @@ public class AbstractCorrelatingMessageHandlerTests {
AggregatingMessageHandler handler = new AggregatingMessageHandler(
new MessageGroupProcessor() {
@Override
public Object processMessageGroup(MessageGroup group) {
return group;
}
@@ -220,17 +235,20 @@ public class AbstractCorrelatingMessageHandlerTests {
/*
* Executes when group 'bar' completes normally
*/
@Override
public boolean send(Message<?> message, long timeout) {
outputMessages.add(message);
return true;
}
@Override
public boolean send(Message<?> message) {
return this.send(message, 0);
}
});
handler.setReleaseStrategy(new ReleaseStrategy() {
@Override
public boolean canRelease(MessageGroup group) {
return group.size() == 1;
}
@@ -258,6 +276,7 @@ public class AbstractCorrelatingMessageHandlerTests {
MessageGroupProcessor mgp = new DefaultAggregatingMessageGroupProcessor();
AggregatingMessageHandler handler = new AggregatingMessageHandler(mgp);
handler.setReleaseStrategy(new ReleaseStrategy() {
@Override
public boolean canRelease(MessageGroup group) {
return true;
}
@@ -283,4 +302,101 @@ public class AbstractCorrelatingMessageHandlerTests {
assertEquals(1, payload.size());
}
@Test /* INT-3216 */
public void testDontReapIfAlreadyComplete() throws Exception {
MessageGroupProcessor mgp = new DefaultAggregatingMessageGroupProcessor();
AggregatingMessageHandler handler = new AggregatingMessageHandler(mgp);
handler.setReleaseStrategy(new ReleaseStrategy() {
@Override
public boolean canRelease(MessageGroup group) {
return true;
}
});
QueueChannel outputChannel = new QueueChannel();
handler.setOutputChannel(outputChannel);
MessageGroupStore mgs = TestUtils.getPropertyValue(handler, "messageStore", MessageGroupStore.class);
mgs.addMessageToGroup("foo", new GenericMessage<String>("foo"));
mgs.completeGroup("foo");
mgs = spy(mgs);
new DirectFieldAccessor(handler).setPropertyValue("messageStore", mgs);
Method forceComplete = AbstractCorrelatingMessageHandler.class.getDeclaredMethod("forceComplete", MessageGroup.class);
forceComplete.setAccessible(true);
MessageGroup group = (MessageGroup) TestUtils.getPropertyValue(mgs, "groupIdToMessageGroup", Map.class).get("foo");
assertTrue(group.isComplete());
forceComplete.invoke(handler, group);
verify(mgs, never()).getMessageGroup("foo");
assertNull(outputChannel.receive(0));
}
/*
* INT-3216 - Verifies the complete early exit is taken after a refresh.
*/
@Test
public void testDontReapIfAlreadyCompleteAfterRefetch() throws Exception {
MessageGroupProcessor mgp = new DefaultAggregatingMessageGroupProcessor();
AggregatingMessageHandler handler = new AggregatingMessageHandler(mgp);
handler.setReleaseStrategy(new ReleaseStrategy() {
@Override
public boolean canRelease(MessageGroup group) {
return true;
}
});
QueueChannel outputChannel = new QueueChannel();
handler.setOutputChannel(outputChannel);
MessageGroupStore mgs = TestUtils.getPropertyValue(handler, "messageStore", MessageGroupStore.class);
mgs.addMessageToGroup("foo", new GenericMessage<String>("foo"));
MessageGroup group = mgs.getMessageGroup("foo");
mgs.completeGroup("foo");
mgs = spy(mgs);
new DirectFieldAccessor(handler).setPropertyValue("messageStore", mgs);
Method forceComplete = AbstractCorrelatingMessageHandler.class.getDeclaredMethod("forceComplete", MessageGroup.class);
forceComplete.setAccessible(true);
MessageGroup groupInStore = (MessageGroup) TestUtils.getPropertyValue(mgs, "groupIdToMessageGroup", Map.class).get("foo");
assertTrue(groupInStore.isComplete());
assertFalse(group.isComplete());
new DirectFieldAccessor(group).setPropertyValue("lastModified", groupInStore.getLastModified());
forceComplete.invoke(handler, group);
verify(mgs).getMessageGroup("foo");
assertNull(outputChannel.receive(0));
}
/*
* INT-3216 - Verifies we don't complete if it's a completely new group (different timestamp).
*/
@Test
public void testDontReapIfNewGroupFoundDuringRefetch() throws Exception {
MessageGroupProcessor mgp = new DefaultAggregatingMessageGroupProcessor();
AggregatingMessageHandler handler = new AggregatingMessageHandler(mgp);
handler.setReleaseStrategy(new ReleaseStrategy() {
@Override
public boolean canRelease(MessageGroup group) {
return true;
}
});
QueueChannel outputChannel = new QueueChannel();
handler.setOutputChannel(outputChannel);
MessageGroupStore mgs = TestUtils.getPropertyValue(handler, "messageStore", MessageGroupStore.class);
mgs.addMessageToGroup("foo", new GenericMessage<String>("foo"));
MessageGroup group = mgs.getMessageGroup("foo");
mgs = spy(mgs);
new DirectFieldAccessor(handler).setPropertyValue("messageStore", mgs);
Method forceComplete = AbstractCorrelatingMessageHandler.class.getDeclaredMethod("forceComplete", MessageGroup.class);
forceComplete.setAccessible(true);
MessageGroup groupInStore = (MessageGroup) TestUtils.getPropertyValue(mgs, "groupIdToMessageGroup", Map.class).get("foo");
assertFalse(groupInStore.isComplete());
assertFalse(group.isComplete());
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(group);
directFieldAccessor.setPropertyValue("lastModified", groupInStore.getLastModified());
directFieldAccessor.setPropertyValue("timestamp", groupInStore.getTimestamp() - 1);
forceComplete.invoke(handler, group);
verify(mgs).getMessageGroup("foo");
assertNull(outputChannel.receive(0));
}
}

View File

@@ -308,10 +308,9 @@ public class MethodInvokingMessageGroupProcessorTests {
assertTrue(((Message<?>)result).getPayload() instanceof Iterator<?>);
}
@Test(expected = IllegalArgumentException.class)
@Test
public void testTwoMethodsWithSameParameterTypesAmbiguous() {
@SuppressWarnings("unused")
class AnnotatedParametersAggregator {
public Integer and(List<Integer> flags) {
int result = 0;
@@ -327,7 +326,12 @@ public class MethodInvokingMessageGroupProcessorTests {
}
}
new MethodInvokingMessageGroupProcessor(new AnnotatedParametersAggregator());
MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new AnnotatedParametersAggregator());
when(messageGroupMock.getMessages()).thenReturn(messagesUpForProcessing);
Object result = processor.processMessageGroup(messageGroupMock);
Object payload = ((Message<?>) result).getPayload();
assertTrue(payload instanceof Integer);
assertEquals(7, payload);
}

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<int:channel id="input" />
<int:header-enricher input-channel="input" output-channel="next">
<int:header-channels-to-string />
</int:header-enricher>
<int:transformer input-channel="next">
<bean class="org.springframework.integration.channel.registry.HeaderChannelRegistryTests$Foo" />
</int:transformer>
<int:channel id="inputPolled">
<int:queue />
</int:channel>
<int:header-enricher input-channel="inputPolled" output-channel="nextPolled">
<int:header-channels-to-string />
<int:poller fixed-delay="100" />
</int:header-enricher>
<int:transformer input-channel="nextPolled">
<bean class="org.springframework.integration.channel.registry.HeaderChannelRegistryTests$Foo" />
</int:transformer>
<int:channel id="alreadyAString">
<int:queue />
</int:channel>
<int:channel id="alreadyAnotherString">
<int:queue />
</int:channel>
<int:gateway id="gatewayNoReplyChannel"
service-interface="org.springframework.integration.channel.registry.HeaderChannelRegistryTests$Gateway"
default-request-channel="inputGateNoReplyChannel" />
<int:channel id="inputGateNoReplyChannel" />
<int:header-enricher input-channel="inputGateNoReplyChannel" output-channel="nextGateNoReplyChannel">
<int:header-channels-to-string />
</int:header-enricher>
<int:transformer input-channel="nextGateNoReplyChannel">
<bean class="org.springframework.integration.channel.registry.HeaderChannelRegistryTests$Foo" />
</int:transformer>
<int:gateway id="gatewayExplicitReplyChannel"
service-interface="org.springframework.integration.channel.registry.HeaderChannelRegistryTests$Gateway"
default-request-channel="inputGateExplicitReplyChannel" default-reply-channel="reply" />
<int:channel id="inputGateExplicitReplyChannel" />
<int:header-enricher input-channel="inputGateExplicitReplyChannel" output-channel="nextGateExplicitReplyChannel">
<int:header-channels-to-string />
</int:header-enricher>
<int:transformer input-channel="nextGateExplicitReplyChannel" output-channel="reply">
<bean class="org.springframework.integration.channel.registry.HeaderChannelRegistryTests$Foo" />
</int:transformer>
<int:channel id="reply" />
</beans>

View File

@@ -0,0 +1,168 @@
/*
* Copyright 2013 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.channel.registry;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.channel.DefaultHeaderChannelRegistry;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.MessagePublishingErrorHandler;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.core.GenericMessagingTemplate;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Gary Russell
* @since 3.0
*
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class HeaderChannelRegistryTests {
@Autowired
MessageChannel input;
@Autowired
MessageChannel inputPolled;
@Autowired
QueueChannel alreadyAString;
@Autowired
TaskScheduler taskScheduler;
@Autowired
Gateway gatewayNoReplyChannel;
@Autowired
Gateway gatewayExplicitReplyChannel;
@Test
public void testReplace() {
GenericMessagingTemplate template = new GenericMessagingTemplate();
template.setDefaultDestination(this.input);
Message<?> reply = template.sendAndReceive(new GenericMessage<String>("foo"));
assertNotNull(reply);
assertEquals("echo:foo", reply.getPayload());
}
@Test
public void testReplaceGatewayWithNoReplyChannel() {
String reply = this.gatewayNoReplyChannel.exchange("foo");
assertNotNull(reply);
assertEquals("echo:foo", reply);
}
@Test
public void testReplaceGatewayWithExplicitReplyChannel() {
String reply = this.gatewayExplicitReplyChannel.exchange("foo");
assertNotNull(reply);
assertEquals("echo:foo", reply);
}
/**
* MessagingTemplate sets the errorChannel to the replyChannel so it gets any async
* exceptions via the default {@link MessagePublishingErrorHandler}.
*/
@Test
public void testReplaceError() {
GenericMessagingTemplate template = new GenericMessagingTemplate();
template.setDefaultDestination(this.inputPolled);
Message<?> reply = template.sendAndReceive(new GenericMessage<String>("bar"));
assertNotNull(reply);
assertTrue(reply instanceof ErrorMessage);
}
@Test
public void testAlreadyAString() {
Message<String> requestMessage = MessageBuilder.withPayload("foo")
.setReplyChannelName("alreadyAString")
.setErrorChannelName("alreadyAnotherString")
.build();
this.input.send(requestMessage);
Message<?> reply = alreadyAString.receive(0);
assertNotNull(reply);
assertEquals("echo:foo", reply.getPayload());
}
@Test
public void testNull() {
Message<String> requestMessage = MessageBuilder.withPayload("foo")
.build();
try {
this.input.send(requestMessage);
fail("expected exception");
}
catch (Exception e) {
assertThat(e.getMessage(), Matchers.containsString("no output-channel or replyChannel"));
}
}
@Test
public void testExpire() throws Exception {
DefaultHeaderChannelRegistry registry = new DefaultHeaderChannelRegistry(50);
registry.setTaskScheduler(this.taskScheduler);
registry.start();
Thread.sleep(200);
String id = (String) registry.channelToChannelName(new DirectChannel());
Thread.sleep(300);
assertNull(registry.channelNameToChannel(id));
registry.stop();
}
public static class Foo extends AbstractReplyProducingMessageHandler {
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
assertThat(requestMessage.getHeaders().getReplyChannel(),
Matchers.anyOf(instanceOf(String.class), Matchers.nullValue()));
assertThat(requestMessage.getHeaders().getErrorChannel(),
Matchers.anyOf(instanceOf(String.class), Matchers.nullValue()));
if (requestMessage.getPayload().equals("bar")) {
throw new RuntimeException("intentional");
}
return "echo:" + requestMessage.getPayload();
}
}
public interface Gateway {
String exchange(String foo);
}
}

View File

@@ -19,18 +19,19 @@ package org.springframework.integration.config;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
@@ -121,14 +122,14 @@ public class AggregatorParserTests {
Object consumer = new DirectFieldAccessor(endpoint).getPropertyValue("handler");
assertThat(consumer, is(instanceOf(AggregatingMessageHandler.class)));
DirectFieldAccessor accessor = new DirectFieldAccessor(consumer);
Map<?, ?> map = (Map<?, ?>) new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(accessor
Object handlerMethods = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(accessor
.getPropertyValue("outputProcessor")).getPropertyValue("processor")).getPropertyValue("delegate"))
.getPropertyValue("handlerMethods");
assertEquals("The MethodInvokingAggregator is not injected with the appropriate aggregation method", 1, map
.size());
assertEquals("The release strategy is not injected with the appropriate method", 1, map.size());
assertTrue("Handler methods do not contain correct method: " + map, map.toString().contains(
"createSingleMessageFromGroup"));
assertNull(handlerMethods);
Object handlerMethod = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(accessor
.getPropertyValue("outputProcessor")).getPropertyValue("processor")).getPropertyValue("delegate"))
.getPropertyValue("handlerMethod");
assertTrue(handlerMethod.toString().contains("createSingleMessageFromGroup"));
assertEquals("The AggregatorEndpoint is not injected with the appropriate ReleaseStrategy instance",
releaseStrategy, accessor.getPropertyValue("releaseStrategy"));
assertEquals("The AggregatorEndpoint is not injected with the appropriate CorrelationStrategy instance",
@@ -178,10 +179,10 @@ public class AggregatorParserTests {
Assert.assertTrue(releaseStrategy instanceof MethodInvokingReleaseStrategy);
DirectFieldAccessor releaseStrategyAccessor = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategy)
.getPropertyValue("adapter")).getPropertyValue("delegate"));
Map<?, ?> map = (Map<?, ?>) releaseStrategyAccessor.getPropertyValue("handlerMethods");
assertEquals("The release strategy is not injected with the appropriate method", 1, map.size());
assertTrue("Handler methods do not contain correct method: " + map, map.toString()
.contains("checkCompleteness"));
Object handlerMethods = releaseStrategyAccessor.getPropertyValue("handlerMethods");
assertNull(handlerMethods);
Object handlerMethod = releaseStrategyAccessor.getPropertyValue("handlerMethod");
assertTrue(handlerMethod.toString().contains("checkCompleteness"));
input.send(createMessage(1l, "correllationId", 4, 0, null));
input.send(createMessage(2l, "correllationId", 4, 1, null));
input.send(createMessage(3l, "correllationId", 4, 2, null));
@@ -203,10 +204,10 @@ public class AggregatorParserTests {
Assert.assertTrue(releaseStrategy instanceof MethodInvokingReleaseStrategy);
DirectFieldAccessor releaseStrategyAccessor = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategy)
.getPropertyValue("adapter")).getPropertyValue("delegate"));
Map<?, ?> map = (Map<?, ?>) releaseStrategyAccessor.getPropertyValue("handlerMethods");
assertEquals("The release strategy is not injected with the appropriate method", 1, map.size());
assertTrue("Handler methods do not contain correct method: " + map, map.toString()
.contains("checkCompleteness"));
Object handlerMethods = releaseStrategyAccessor.getPropertyValue("handlerMethods");
assertNull(handlerMethods);
Object handlerMethod = releaseStrategyAccessor.getPropertyValue("handlerMethod");
assertTrue(handlerMethod.toString().contains("checkCompleteness"));
input.send(createMessage(1l, "correllationId", 4, 0, null));
input.send(createMessage(2l, "correllationId", 4, 1, null));
input.send(createMessage(3l, "correllationId", 4, 2, null));

View File

@@ -40,7 +40,7 @@
<chain input-channel="pollableInput1" output-channel="output">
<filter ref="typeSelector"/>
<poller fixed-delay="10000"/>
<poller fixed-delay="1000"/>
<service-activator ref="testHandler"/>
</chain>
@@ -49,7 +49,7 @@
<poller ref="topLevelPoller"/>
</chain>
<poller id="topLevelPoller" fixed-delay="5000"/>
<poller id="topLevelPoller" fixed-delay="1000"/>
<chain input-channel="beanInput" output-channel="output">
<beans:bean
@@ -67,7 +67,7 @@
</chain>
</chain>
<chain id="aggregatorChain2" input-channel="aggregatorInput" output-channel="output">
<chain id="aggregatorChain2" input-channel="aggregator2Input" output-channel="output">
<aggregator id="aggregatorWithinChain" ref="aggregatorBean" method="aggregate"/>
<chain id="nestedChain">
<filter id="filterWithinNestedChain" ref="typeSelector"/>

View File

@@ -172,7 +172,7 @@ public class ChainParserTests {
public void chainWithAcceptingFilter() {
Message<?> message = MessageBuilder.withPayload("test").build();
this.filterInput.send(message);
Message<?> reply = this.output.receive(0);
Message<?> reply = this.output.receive(1000);
assertNotNull(reply);
assertEquals("foo", reply.getPayload());
}
@@ -189,7 +189,7 @@ public class ChainParserTests {
public void chainWithHeaderEnricher() {
Message<?> message = MessageBuilder.withPayload(123).build();
this.headerEnricherInput.send(message);
Message<?> reply = this.replyOutput.receive(0);
Message<?> reply = this.replyOutput.receive(1000);
assertNotNull(reply);
assertEquals("foo", reply.getPayload());
assertEquals("ABC", new EiMessageHeaderAccessor(reply).getCorrelationId());
@@ -240,8 +240,8 @@ public class ChainParserTests {
Message<?> message2 = MessageBuilder.withPayload(123).build();
this.payloadTypeRouterInput.send(message1);
this.payloadTypeRouterInput.send(message2);
Message<?> reply1 = this.strings.receive(0);
Message<?> reply2 = this.numbers.receive(0);
Message<?> reply1 = this.strings.receive(1000);
Message<?> reply2 = this.numbers.receive(1000);
assertNotNull(reply1);
assertNotNull(reply2);
assertEquals("test", reply1.getPayload());
@@ -254,8 +254,8 @@ public class ChainParserTests {
Message<?> message2 = MessageBuilder.withPayload(123).setHeader("routingHeader", "numbers").build();
this.headerValueRouterInput.send(message1);
this.headerValueRouterInput.send(message2);
Message<?> reply1 = this.strings.receive(0);
Message<?> reply2 = this.numbers.receive(0);
Message<?> reply1 = this.strings.receive(1000);
Message<?> reply2 = this.numbers.receive(1000);
assertNotNull(reply1);
assertNotNull(reply2);
assertEquals("test", reply1.getPayload());
@@ -302,6 +302,7 @@ public class ChainParserTests {
final AtomicReference<String> log = new AtomicReference<String>();
when(logger.isWarnEnabled()).thenReturn(true);
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
log.set((String) invocation.getArguments()[0]);
return null;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2013 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,16 +17,17 @@
package org.springframework.integration.config.annotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.springframework.integration.test.util.TestUtils.getPropertyValue;
import java.lang.reflect.Method;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -45,6 +46,7 @@ import org.springframework.messaging.core.DestinationResolver;
/**
* @author Marius Bogoevici
* @author Mark Fisher
* @author Artem Bilan
*/
public class AggregatorAnnotationTests {
@@ -86,13 +88,12 @@ public class AggregatorAnnotationTests {
Object releaseStrategy = getPropertyValue(aggregator, "releaseStrategy");
Assert.assertTrue(releaseStrategy instanceof MethodInvokingReleaseStrategy);
MethodInvokingReleaseStrategy releaseStrategyAdapter = (MethodInvokingReleaseStrategy) releaseStrategy;
Map<?, ?> map = (Map<?, ?>) new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategyAdapter)
Object handlerMethods = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategyAdapter)
.getPropertyValue("adapter")).getPropertyValue("delegate")).getPropertyValue("handlerMethods");
assertEquals("The MethodInvokingAggregator is not injected with the appropriate aggregation method", 1, map
.size());
assertEquals("The release strategy is not injected with the appropriate method", 1, map.size());
assertTrue("Handler methods do not contain correct method: " + map, map.toString()
.contains("completionChecker"));
assertNull(handlerMethods);
Object handlerMethod = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategyAdapter)
.getPropertyValue("adapter")).getPropertyValue("delegate")).getPropertyValue("handlerMethod");
assertTrue(handlerMethod.toString().contains("completionChecker"));
}
@Test
@@ -108,9 +109,10 @@ public class AggregatorAnnotationTests {
.getPropertyValue("processor")).getPropertyValue("delegate"));
Object targetObject = processorAccessor.getPropertyValue("targetObject");
assertSame(context.getBean(endpointName), targetObject);
Map<?, ?> handlerMethods = (Map<?, ?>) processorAccessor.getPropertyValue("handlerMethods");
assertEquals(1, handlerMethods.size());
DirectFieldAccessor handlerMethodAccessor = new DirectFieldAccessor(handlerMethods.values().iterator().next());
assertNull(processorAccessor.getPropertyValue("handlerMethods"));
Object handlerMethod = processorAccessor.getPropertyValue("handlerMethod");
assertNotNull(handlerMethod);
DirectFieldAccessor handlerMethodAccessor = new DirectFieldAccessor(handlerMethod);
Method completionCheckerMethod = (Method) handlerMethodAccessor.getPropertyValue("method");
assertEquals("correlate", completionCheckerMethod.getName());
}

View File

@@ -16,8 +16,8 @@
package org.springframework.integration.config.xml;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.util.Date;
@@ -28,12 +28,15 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.channel.DefaultHeaderChannelRegistry;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.core.GenericMessagingTemplate;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -52,6 +55,9 @@ public class ControlBusTests {
@Autowired
private PollableChannel output;
@Autowired
private DefaultHeaderChannelRegistry registry;
@Test
public void testDefaultEvaluationContext() {
Message<?> message = MessageBuilder.withPayload("@service.convert('aardvark')+headers.foo").setHeader("foo", "bar").build();
@@ -71,6 +77,27 @@ public class ControlBusTests {
assertNotNull(outputChannel.receive(1000));
}
@Test
public void testControlHeaderChannelReaper() throws InterruptedException {
GenericMessagingTemplate messagingTemplate = new GenericMessagingTemplate();
messagingTemplate.convertAndSend(input, "@integrationHeaderChannelRegistry.size()");
Message<?> result = this.output.receive(0);
assertNotNull(result);
assertEquals(0, result.getPayload());
this.registry.setReaperDelay(10);
this.registry.channelToChannelName(new DirectChannel());
messagingTemplate.convertAndSend(input, "@integrationHeaderChannelRegistry.size()");
result = this.output.receive(0);
assertNotNull(result);
assertEquals(1, result.getPayload());
Thread.sleep(100);
messagingTemplate.convertAndSend(input, "@integrationHeaderChannelRegistry.runReaper()");
messagingTemplate.convertAndSend(input, "@integrationHeaderChannelRegistry.size()");
result = this.output.receive(0);
assertNotNull(result);
assertEquals(0, result.getPayload());
this.registry.setReaperDelay(60000);
}
public static class Service {
@@ -78,12 +105,14 @@ public class ControlBusTests {
public String convert(String input) {
return "cat";
}
}
public static class AdapterService {
public Message<String> receive() {
return new GenericMessage<String>(new Date().toString());
}
}
}

View File

@@ -13,11 +13,17 @@
<queue />
</channel>
<header-enricher input-channel="requests1" output-channel="requests">
<header-channels-to-string />
</header-enricher>
<channel id="requests"/>
<channel id="replies"/>
<enricher id="enricher" input-channel="input"
request-channel="requests" request-timeout="1234"
reply-timeout="9876"
request-channel="requests1" request-timeout="1234"
reply-timeout="9876" reply-channel="replies"
order="99" should-clone-payload="true" output-channel="output">
<property name="name" expression="payload.sourceName"/>
<property name="age" value="42"/>

View File

@@ -17,6 +17,7 @@
package org.springframework.integration.config.xml;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
@@ -79,6 +80,7 @@ public class EnricherParserTests {
assertEquals(context.getBean("output"), accessor.getPropertyValue("outputChannel"));
assertEquals(true, accessor.getPropertyValue("shouldClonePayload"));
assertNull(accessor.getPropertyValue("requestPayloadExpression"));
assertNotNull(TestUtils.getPropertyValue(enricher, "gateway.beanFactory"));
Map<Expression, Expression> propertyExpressions = (Map<Expression, Expression>) accessor.getPropertyValue("propertyExpressions");
for (Map.Entry<Expression, Expression> e : propertyExpressions.entrySet()) {
@@ -125,12 +127,15 @@ public class EnricherParserTests {
@Test
public void integrationTest() {
SubscribableChannel requests = context.getBean("requests", SubscribableChannel.class);
requests.subscribe(new AbstractReplyProducingMessageHandler() {
class Foo extends AbstractReplyProducingMessageHandler {
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
return new Source("foo");
}
});
};
Foo foo = new Foo();
foo.setOutputChannel(context.getBean("replies", MessageChannel.class));
requests.subscribe(foo);
Target original = new Target();
Message<?> request = MessageBuilder.withPayload(original)
.setHeader("sourceName", "test")

View File

@@ -7,7 +7,7 @@
<int:service-activator input-channel="inputChannel" expression="'hello'"/>
<bean id="ChannelInitializer" class="org.springframework.integration.config.xml.ChannelInitializer">
<bean id="channelInitializer" class="org.springframework.integration.config.xml.ChannelInitializer">
<property name="autoCreate" value="true"/>
</bean>

View File

@@ -5,7 +5,7 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<bean id="ChannelInitializer" class="org.springframework.integration.config.xml.ChannelInitializer">
<bean id="channelInitializer" class="org.springframework.integration.config.xml.ChannelInitializer">
<property name="autoCreate" value="true"/>
</bean>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
<channel id="foo"/>
<service-activator id="fooService" input-channel="foo" output-channel="nullChannel" expression="'foo'"/>
</beans:beans>

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2013 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.context;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import java.util.Properties;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Artem Bilan
* @since 3.0
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class IntegrationContextTests {
@Autowired
@Qualifier(IntegrationContextUtils.INTEGRATION_PROPERTIES_BEAN_NAME)
private Properties integrationProperties;
@Autowired
@Qualifier("fooService")
private IntegrationObjectSupport serviceActivator;
@Test
public void testIntegrationContextComponents() {
assertEquals("error", this.integrationProperties.get(IntegrationProperties.LATE_REPLY_LOGGING_LEVEL));
assertSame(this.integrationProperties, this.serviceActivator.getIntegrationProperties());
}
}

View File

@@ -13,7 +13,7 @@
<si:channel id="producerAndConsumerAutoStartupTestChannel"/>
<si:service-activator id="consumerEndpoint" input-channel="producerAndConsumerAutoStartupTestChannel" ref="consumer"/>
<si:service-activator id="consumerEndpoint" input-channel="producerAndConsumerAutoStartupTestChannel" ref="consumer" phase="-100"/>
<bean id="counter" class="org.springframework.integration.endpoint.ProducerAndConsumerAutoStartupTests$Counter"/>

View File

@@ -17,13 +17,19 @@
package org.springframework.integration.endpoint;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertSame;
import java.util.Date;
import org.junit.Test;
import org.springframework.messaging.Message;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.gateway.RequestReplyExchanger;
import org.springframework.integration.handler.ServiceActivatingHandler;
import org.springframework.messaging.Message;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
/**
@@ -66,6 +72,166 @@ public class ServiceActivatorMethodResolutionTests {
}
@Test
public void testRequestReplyExchanger() {
RequestReplyExchanger testBean = new RequestReplyExchanger() {
@Override
public Message<?> exchange(Message<?> request) {
return request;
}
};
final Message<?> test = new GenericMessage<Object>("foo");
ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean) {
@Override
protected Object handleRequestMessage(Message<?> message) {
Object o = super.handleRequestMessage(message);
assertSame(test, o);
return null;
}
};
serviceActivator.handleMessage(test);
}
@Test
/*
* A handler and message handler fallback (RRE); don't force RRE
*/
public void testRequestReplyExchangerSeveralMethods() {
RequestReplyExchanger testBean = new RequestReplyExchanger() {
@Override
public Message<?> exchange(Message<?> request) {
return request;
}
@SuppressWarnings("unused")
public String foo(String request) {
return request.toUpperCase();
}
};
ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean);
PollableChannel outputChannel = new QueueChannel();
serviceActivator.setOutputChannel(outputChannel);
Message<?> test = new GenericMessage<Object>(new Date());
serviceActivator.handleMessage(test);
assertEquals(test, outputChannel.receive(10));
test = new GenericMessage<Object>("foo");
serviceActivator.handleMessage(test);
assertEquals("FOO", outputChannel.receive(10).getPayload());
}
@Test
/*
* No handler fallback methods; don't force RRE
*/
public void testRequestReplyExchangerWithGenericMessageMethod() {
RequestReplyExchanger testBean = new RequestReplyExchanger() {
@Override
public Message<?> exchange(Message<?> request) {
return request;
}
@SuppressWarnings("unused")
public String foo(Message<String> request) {
return request.getPayload().toUpperCase();
}
};
ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean);
PollableChannel outputChannel = new QueueChannel();
serviceActivator.setOutputChannel(outputChannel);
Message<?> test = new GenericMessage<Object>(new Date());
serviceActivator.handleMessage(test);
assertEquals(test, outputChannel.receive(10));
test = new GenericMessage<Object>("foo");
serviceActivator.handleMessage(test);
assertEquals("FOO", outputChannel.receive(10).getPayload());
}
@Test
/*
* No handler fallback methods; ambiguous message handler fallbacks; force RRE
*/
public void testRequestReplyExchangerWithAmbiguousGenericMessageMethod() {
RequestReplyExchanger testBean = new RequestReplyExchanger() {
@Override
public Message<?> exchange(Message<?> request) {
return request;
}
@SuppressWarnings("unused")
public String foo(Message<String> request) {
return request.getPayload().toUpperCase();
}
@SuppressWarnings("unused")
public String bar(Message<String> request) {
return request.getPayload().toUpperCase();
}
};
ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean);
PollableChannel outputChannel = new QueueChannel();
serviceActivator.setOutputChannel(outputChannel);
Message<?> test = new GenericMessage<Object>(new Date());
serviceActivator.handleMessage(test);
assertEquals(test, outputChannel.receive(10));
test = new GenericMessage<Object>("foo");
serviceActivator.handleMessage(test);
assertNotEquals("FOO", outputChannel.receive(10).getPayload());
}
@Test
/*
* One message handler fallback method (RRE); ambiguous handler fallbacks; force RRE
*/
public void testRequestReplyExchangerWithAmbiguousMethod() {
RequestReplyExchanger testBean = new RequestReplyExchanger() {
@Override
public Message<?> exchange(Message<?> request) {
return request;
}
@SuppressWarnings("unused")
public String foo(String request) {
return request.toUpperCase();
}
@SuppressWarnings("unused")
public String bar(String request) {
return request.toUpperCase();
}
};
ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean);
PollableChannel outputChannel = new QueueChannel();
serviceActivator.setOutputChannel(outputChannel);
Message<?> test = new GenericMessage<Object>(new Date());
serviceActivator.handleMessage(test);
assertEquals(test, outputChannel.receive(10));
test = new GenericMessage<Object>("foo");
serviceActivator.handleMessage(test);
assertNotEquals("FOO", outputChannel.receive(10).getPayload());
}
@SuppressWarnings("unused")
private static class SingleAnnotationTestBean {

View File

@@ -20,18 +20,23 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hamcrest.Description;
import org.hamcrest.Matchers;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.messaging.Message;
import org.springframework.integration.MessageHandlingException;
@@ -355,6 +360,135 @@ public class MethodInvokingMessageProcessorTests {
assertSame(RequestReplyExchanger.class, result);
}
@Test
public void testInt3199GenericTypeResolvingAndObjectMethod() throws Exception {
class Foo {
public String handleMessage(Message<Number> message) {
return "" + (message.getPayload().intValue() * 2);
}
public String objectMethod(Integer foo) {
return foo.toString();
}
public String voidMethod() {
return "foo";
}
}
MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(new Foo(), (String) null, false);
assertEquals("4", helper.process(new GenericMessage<Object>(2L)));
assertEquals("1", helper.process(new GenericMessage<Object>(1)));
assertEquals("foo", helper.process(new GenericMessage<Object>(new Date())));
}
@Test
public void testInt3199GettersAmbiguity() throws Exception {
class Foo {
public String getFoo() {
return "foo";
}
public String getBar() {
return "foo";
}
}
try {
new MessagingMethodInvokerHelper(new Foo(), (String) null, false);
fail("IllegalArgumentException expected");
}
catch (Exception e) {
assertThat(e, Matchers.instanceOf(IllegalArgumentException.class));
assertEquals("Found more than one method match for empty parameter for 'payload'", e.getMessage());
}
}
@Test
public void testInt3199MessageMethods() throws Exception {
class Foo {
public String m1(Message<String> message) {
return message.getPayload();
}
public Integer m2(Message<Integer> message) {
return message.getPayload();
}
public Object m3(Message<?> message) {
return message.getPayload();
}
}
Foo targetObject = new Foo();
MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false);
assertEquals("foo", helper.process(new GenericMessage<Object>("foo")));
assertEquals(1, helper.process(new GenericMessage<Object>(1)));
assertEquals(targetObject, helper.process(new GenericMessage<Object>(targetObject)));
}
@Test
public void testInt3199TypedMethods() throws Exception {
class Foo {
public String m1(String payload) {
return payload;
}
public Integer m2(Integer payload) {
return payload;
}
public Object m3(Object payload) {
return payload;
}
}
Foo targetObject = new Foo();
MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false);
assertEquals("foo", helper.process(new GenericMessage<Object>("foo")));
assertEquals(1, helper.process(new GenericMessage<Object>(1)));
assertEquals(targetObject, helper.process(new GenericMessage<Object>(targetObject)));
}
@Test
public void testInt3199PrecedenceOfCandidates() throws Exception {
class Foo {
public Object m1(Message<String> message) {
fail("This method must not be invoked");
return message;
}
public Object m2(String payload) {
return payload;
}
public Object m3() {
return "FOO";
}
}
Foo targetObject = new Foo();
MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false);
assertEquals("foo", helper.process(new GenericMessage<Object>("foo")));
assertEquals("FOO", helper.process(new GenericMessage<Object>(targetObject)));
}
private static class ExceptionCauseMatcher extends TypeSafeMatcher<Exception> {
private Throwable cause;
@@ -528,5 +662,7 @@ public class MethodInvokingMessageProcessorTests {
this.lastArg = s;
return s;
}
}
}

View File

@@ -53,6 +53,7 @@ import com.jayway.jsonpath.Filter;
/**
* @author Artem Bilan
* @author Gary Russell
* @since 3.0
*/
@ContextConfiguration(classes = JsonPathTests.JsonPathTestsContextConfiguration.class, loader = AnnotationConfigContextLoader.class)
@@ -69,7 +70,9 @@ public class JsonPathTests {
public static void setUp() throws IOException {
ClassPathResource jsonResource = new ClassPathResource("JsonPathTests.json", JsonPathTests.class);
JSON_FILE = jsonResource.getFile();
JSON = new Scanner(JSON_FILE).useDelimiter("\\Z").next();
Scanner scanner = new Scanner(JSON_FILE);
JSON = scanner.useDelimiter("\\Z").next();
scanner.close();
testMessage = new GenericMessage<String>(JSON);
}

View File

@@ -749,14 +749,14 @@ public class PayloadAndHeaderMappingTests {
this.lastHeaders.put("foo", header);
this.lastPayload = payload;
}
public void payloadMapAndHeaderStrings(Map payload, @Header("foo") String header1, @Header("bar") String header2) {
this.lastHeaders = new HashMap<String, String>();
this.lastHeaders.put("foo", header1);
this.lastHeaders.put("bar", header2);
this.lastPayload = payload;
this.lastPayload = payload;
}
public void payloadMapAndHeaderMap(Map payload, @Headers Map headers) {
this.lastHeaders = headers;
this.lastPayload = payload;
@@ -771,7 +771,7 @@ public class PayloadAndHeaderMappingTests {
this.lastHeaders = headers;
this.lastPayload = payload;
}
public void headerPropertiesPayloadMapAndStringHeader(@Headers Properties headers, Map payload, @Header("foo") String header) {
this.lastHeaders = headers;
this.lastHeaders.put("foo2", header);

View File

@@ -171,7 +171,7 @@ public class SpelTransformerIntegrationTests {
@Override
public Class<?>[] getSpecificTargetClasses() {
return new Class[] {Foo.class};
return new Class<?>[] {Foo.class};
}
@Override

View File

@@ -0,0 +1,2 @@
#channels.autoCreate=false
messagingTemplate.lateReply.logging.level=error