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:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
channels.autoCreate=true
|
||||
messagingTemplate.lateReply.logging.level=warn
|
||||
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -171,7 +171,7 @@ public class SpelTransformerIntegrationTests {
|
||||
|
||||
@Override
|
||||
public Class<?>[] getSpecificTargetClasses() {
|
||||
return new Class[] {Foo.class};
|
||||
return new Class<?>[] {Foo.class};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
#channels.autoCreate=false
|
||||
messagingTemplate.lateReply.logging.level=error
|
||||
Reference in New Issue
Block a user