INT-2269 Add ReplyChannelRegistry
Allows reply channel resolution after a message has been serialized somewhere in a flow. Previously, the reply channel was lost. With this change, the reply channel can be registered and the header becomes a string (channel name) that can be serialized. JIRA: https://jira.springsource.org/browse/INT-2269 INT-2269 Rename Registry to HeaderChannelRegistry - Extract interface - DefaultHeaderChannelRgistry INT-2269 Polishing; PR Comments - Add errorChannel registration as well. INT-2269 HeaderChannelRegistry - Fix The internal BridgeHandler in MessagingGatewaySupport did not have a channel resolver. When a reply was explicitly routed to the gateway's reply channel, the String representation of the reply channel could not be resolved to a channel. Set the BeanFactory on the bridge handler. Add tests. INT-2269 HeaderChannelRegistry - Fix JMS/Enricher The JMS inbound gateway and ContentEnricher instantiate a MessagingGatewaySupport internally it does not get a reference to the BeanFactory. This means that the internal BridgeHandler cannot resolve the String representation of the reply channel to a channel. Make ChannelPublishingJmsMessageListener BeanFactory aware, and propagate the bean factory to the MGS. Set the MGS bean factory in the ContentEnricher (which is already BFA). INT-2269: Polishing
This commit is contained in:
committed by
Artem Bilan
parent
1d0c28852f
commit
836c8e2556
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* 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 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.MessageChannel;
|
||||||
|
import org.springframework.integration.context.IntegrationObjectSupport;
|
||||||
|
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.springframework.integration.MessageChannel;
|
||||||
|
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
|
||||||
|
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
||||||
|
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.integration.config.xml;
|
package org.springframework.integration.config.xml;
|
||||||
|
|
||||||
import static org.springframework.integration.context.IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
@@ -35,6 +33,7 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
|||||||
import org.springframework.beans.factory.xml.NamespaceHandler;
|
import org.springframework.beans.factory.xml.NamespaceHandler;
|
||||||
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
||||||
import org.springframework.beans.factory.xml.ParserContext;
|
import org.springframework.beans.factory.xml.ParserContext;
|
||||||
|
import org.springframework.integration.channel.registry.DefaultHeaderChannelRegistry;
|
||||||
import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean;
|
import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean;
|
||||||
import org.springframework.integration.config.xml.ChannelInitializer.AutoCreateCandidatesCollector;
|
import org.springframework.integration.config.xml.ChannelInitializer.AutoCreateCandidatesCollector;
|
||||||
import org.springframework.integration.context.IntegrationContextUtils;
|
import org.springframework.integration.context.IntegrationContextUtils;
|
||||||
@@ -68,15 +67,18 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
|
|||||||
private final NamespaceHandlerDelegate delegate = new NamespaceHandlerDelegate();
|
private final NamespaceHandlerDelegate delegate = new NamespaceHandlerDelegate();
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
public final BeanDefinition parse(Element element, ParserContext parserContext) {
|
public final BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||||
this.verifySchemaVersion(element, parserContext);
|
this.verifySchemaVersion(element, parserContext);
|
||||||
this.registerImplicitChannelCreator(parserContext);
|
this.registerImplicitChannelCreator(parserContext);
|
||||||
this.registerIntegrationEvaluationContext(parserContext);
|
this.registerIntegrationEvaluationContext(parserContext);
|
||||||
|
this.registerHeaderChannelRegistry(parserContext);
|
||||||
this.registerBuiltInBeans(parserContext);
|
this.registerBuiltInBeans(parserContext);
|
||||||
this.registerDefaultConfiguringBeanFactoryPostProcessorIfNecessary(parserContext);
|
this.registerDefaultConfiguringBeanFactoryPostProcessorIfNecessary(parserContext);
|
||||||
return this.delegate.parse(element, parserContext);
|
return this.delegate.parse(element, parserContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public final BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext) {
|
public final BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext) {
|
||||||
return this.delegate.decorate(source, definition, parserContext);
|
return this.delegate.decorate(source, definition, parserContext);
|
||||||
}
|
}
|
||||||
@@ -127,10 +129,11 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
|
|||||||
// unlike DefaultConfiguringBeanFactoryPostProcessor, we need one of these per registry
|
// unlike DefaultConfiguringBeanFactoryPostProcessor, we need one of these per registry
|
||||||
// therefore we need to call containsBeanDefinition(..) which does not consider the parent registry
|
// therefore we need to call containsBeanDefinition(..) which does not consider the parent registry
|
||||||
alreadyRegistered = ((ListableBeanFactory) parserContext.getRegistry()).containsBeanDefinition(
|
alreadyRegistered = ((ListableBeanFactory) parserContext.getRegistry()).containsBeanDefinition(
|
||||||
INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
|
IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
|
alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(
|
||||||
|
IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
|
||||||
}
|
}
|
||||||
if (!alreadyRegistered) {
|
if (!alreadyRegistered) {
|
||||||
BeanDefinitionBuilder integrationEvaluationContextBuilder = BeanDefinitionBuilder
|
BeanDefinitionBuilder integrationEvaluationContextBuilder = BeanDefinitionBuilder
|
||||||
@@ -195,6 +198,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) {
|
protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator decorator) {
|
||||||
this.delegate.doRegisterBeanDefinitionDecorator(elementName, decorator);
|
this.delegate.doRegisterBeanDefinitionDecorator(elementName, decorator);
|
||||||
}
|
}
|
||||||
@@ -225,6 +255,7 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
|
|||||||
|
|
||||||
private class NamespaceHandlerDelegate extends NamespaceHandlerSupport {
|
private class NamespaceHandlerDelegate extends NamespaceHandlerSupport {
|
||||||
|
|
||||||
|
@Override
|
||||||
public void init() {
|
public void init() {
|
||||||
AbstractIntegrationNamespaceHandler.this.init();
|
AbstractIntegrationNamespaceHandler.this.init();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import org.springframework.util.xml.DomUtils;
|
|||||||
public abstract class AbstractPollingInboundChannelAdapterParser extends AbstractChannelAdapterParser {
|
public abstract class AbstractPollingInboundChannelAdapterParser extends AbstractChannelAdapterParser {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) {
|
protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) {
|
||||||
BeanMetadataElement source = this.parseSource(element, parserContext);
|
BeanMetadataElement source = this.parseSource(element, parserContext);
|
||||||
if (source == null) {
|
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.BeanDefinitionBuilder;
|
||||||
import org.springframework.beans.factory.support.ManagedMap;
|
import org.springframework.beans.factory.support.ManagedMap;
|
||||||
import org.springframework.beans.factory.xml.ParserContext;
|
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.ClassUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.util.xml.DomUtils;
|
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.
|
* Base support class for 'header-enricher' parsers.
|
||||||
@@ -41,6 +42,7 @@ import org.springframework.integration.transformer.HeaderEnricher;
|
|||||||
* @author Mark Fisher
|
* @author Mark Fisher
|
||||||
* @author Oleg Zhurakousky
|
* @author Oleg Zhurakousky
|
||||||
* @author Artem Bilan
|
* @author Artem Bilan
|
||||||
|
* @author Gary Russell
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public abstract class HeaderEnricherParserSupport extends AbstractTransformerParser {
|
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 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
|
@Override
|
||||||
protected final String getTransformerClassName() {
|
protected final String getTransformerClassName() {
|
||||||
@@ -86,6 +98,8 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
|
|||||||
Element headerElement = (Element) node;
|
Element headerElement = (Element) node;
|
||||||
String elementName = node.getLocalName();
|
String elementName = node.getLocalName();
|
||||||
Class<?> headerType = null;
|
Class<?> headerType = null;
|
||||||
|
String expression = null;
|
||||||
|
String overwrite = headerElement.getAttribute("overwrite");
|
||||||
if ("header".equals(elementName)) {
|
if ("header".equals(elementName)) {
|
||||||
headerName = headerElement.getAttribute(NAME_ATTRIBUTE);
|
headerName = headerElement.getAttribute(NAME_ATTRIBUTE);
|
||||||
}
|
}
|
||||||
@@ -114,135 +128,157 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (headerName != null) {
|
if (headerName == null) {
|
||||||
String value = headerElement.getAttribute("value");
|
if (cannedHeaderElementExpressions.containsKey(elementName)) {
|
||||||
String ref = headerElement.getAttribute(REF_ATTRIBUTE);
|
for (int j = 0; j < cannedHeaderElementExpressions.get(elementName).length; j++) {
|
||||||
String method = headerElement.getAttribute(METHOD_ATTRIBUTE);
|
headerName = cannedHeaderElementExpressions.get(elementName)[j][0];
|
||||||
String expression = headerElement.getAttribute(EXPRESSION_ATTRIBUTE);
|
expression = cannedHeaderElementExpressions.get(elementName)[j][1];
|
||||||
|
overwrite = "true";
|
||||||
Element beanElement = null;
|
this.addHeader(element, headers, parserContext, headerName, headerElement, headerType,
|
||||||
Element scriptElement = null;
|
expression, overwrite);
|
||||||
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);
|
else {
|
||||||
}
|
this.addHeader(element, headers, parserContext, headerName, headerElement, headerType, expression,
|
||||||
|
overwrite);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
* Subclasses may override this method to provide any additional processing.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ public abstract class IntegrationContextUtils {
|
|||||||
|
|
||||||
public static final String INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME = "integrationEvaluationContext";
|
public static final String INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME = "integrationEvaluationContext";
|
||||||
|
|
||||||
|
public static final String INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME = "integrationHeaderChannelRegistry";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the {@link MetadataStore} bean whose name is "metadataStore".
|
* Return the {@link MetadataStore} bean whose name is "metadataStore".
|
||||||
* @param beanFactory BeanFactory for lookup, must not be null.
|
* @param beanFactory BeanFactory for lookup, must not be 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -19,7 +19,6 @@ package org.springframework.integration.gateway;
|
|||||||
import org.springframework.integration.Message;
|
import org.springframework.integration.Message;
|
||||||
import org.springframework.integration.MessageChannel;
|
import org.springframework.integration.MessageChannel;
|
||||||
import org.springframework.integration.MessagingException;
|
import org.springframework.integration.MessagingException;
|
||||||
import org.springframework.integration.core.MessageHandler;
|
|
||||||
import org.springframework.integration.core.MessagingTemplate;
|
import org.springframework.integration.core.MessagingTemplate;
|
||||||
import org.springframework.integration.core.PollableChannel;
|
import org.springframework.integration.core.PollableChannel;
|
||||||
import org.springframework.integration.core.SubscribableChannel;
|
import org.springframework.integration.core.SubscribableChannel;
|
||||||
@@ -43,6 +42,7 @@ import org.springframework.util.Assert;
|
|||||||
* well as the timeout values for sending and receiving Messages.
|
* well as the timeout values for sending and receiving Messages.
|
||||||
*
|
*
|
||||||
* @author Mark Fisher
|
* @author Mark Fisher
|
||||||
|
* @author Gary Russell
|
||||||
*/
|
*/
|
||||||
public abstract class MessagingGatewaySupport extends AbstractEndpoint implements TrackableComponent {
|
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
|
* Specify whether this gateway should be tracked in the Message History
|
||||||
* of Messages that originate from its send or sendAndReceive operations.
|
* of Messages that originate from its send or sendAndReceive operations.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void setShouldTrack(boolean shouldTrack) {
|
public void setShouldTrack(boolean shouldTrack) {
|
||||||
this.historyWritingPostProcessor.setShouldTrack(shouldTrack);
|
this.historyWritingPostProcessor.setShouldTrack(shouldTrack);
|
||||||
}
|
}
|
||||||
@@ -283,7 +284,11 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint implement
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AbstractEndpoint correlator = null;
|
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) {
|
if (this.replyChannel instanceof SubscribableChannel) {
|
||||||
correlator = new EventDrivenConsumer(
|
correlator = new EventDrivenConsumer(
|
||||||
(SubscribableChannel) this.replyChannel, handler);
|
(SubscribableChannel) this.replyChannel, handler);
|
||||||
@@ -320,6 +325,7 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint implement
|
|||||||
|
|
||||||
private static class DefaultRequestMapper implements InboundMessageMapper<Object> {
|
private static class DefaultRequestMapper implements InboundMessageMapper<Object> {
|
||||||
|
|
||||||
|
@Override
|
||||||
public Message<?> toMessage(Object object) throws Exception {
|
public Message<?> toMessage(Object object) throws Exception {
|
||||||
if (object instanceof Message<?>) {
|
if (object instanceof Message<?>) {
|
||||||
return (Message<?>) object;
|
return (Message<?>) object;
|
||||||
|
|||||||
@@ -16,10 +16,15 @@
|
|||||||
|
|
||||||
package org.springframework.integration.support.channel;
|
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.BeansException;
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.BeanFactoryAware;
|
import org.springframework.beans.factory.BeanFactoryAware;
|
||||||
import org.springframework.integration.MessageChannel;
|
import org.springframework.integration.MessageChannel;
|
||||||
|
import org.springframework.integration.channel.registry.HeaderChannelRegistry;
|
||||||
|
import org.springframework.integration.context.IntegrationContextUtils;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,8 +38,11 @@ import org.springframework.util.Assert;
|
|||||||
*/
|
*/
|
||||||
public class BeanFactoryChannelResolver implements ChannelResolver, BeanFactoryAware {
|
public class BeanFactoryChannelResolver implements ChannelResolver, BeanFactoryAware {
|
||||||
|
|
||||||
|
private final static Log logger = LogFactory.getLog(BeanFactoryChannelResolver.class);
|
||||||
|
|
||||||
private volatile BeanFactory beanFactory;
|
private volatile BeanFactory beanFactory;
|
||||||
|
|
||||||
|
private volatile HeaderChannelRegistry replyChannelRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance of the {@link BeanFactoryChannelResolver} class.
|
* Create a new instance of the {@link BeanFactoryChannelResolver} class.
|
||||||
@@ -58,20 +66,40 @@ public class BeanFactoryChannelResolver implements ChannelResolver, BeanFactoryA
|
|||||||
*/
|
*/
|
||||||
public BeanFactoryChannelResolver(BeanFactory beanFactory) {
|
public BeanFactoryChannelResolver(BeanFactory beanFactory) {
|
||||||
Assert.notNull(beanFactory, "BeanFactory must not be null");
|
Assert.notNull(beanFactory, "BeanFactory must not be null");
|
||||||
this.beanFactory = beanFactory;
|
this.lookupHeaderChannelRegistry(beanFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setBeanFactory(BeanFactory beanFactory) {
|
public void setBeanFactory(BeanFactory beanFactory) {
|
||||||
this.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 resolveChannelName(String name) {
|
public MessageChannel resolveChannelName(String name) {
|
||||||
Assert.state(this.beanFactory != null, "BeanFactory is required");
|
Assert.state(this.beanFactory != null, "BeanFactory is required");
|
||||||
try {
|
try {
|
||||||
return this.beanFactory.getBean(name, MessageChannel.class);
|
return this.beanFactory.getBean(name, MessageChannel.class);
|
||||||
}
|
}
|
||||||
catch (BeansException e) {
|
catch (BeansException e) {
|
||||||
|
if (this.replyChannelRegistry != null) {
|
||||||
|
MessageChannel channel = this.replyChannelRegistry.channelNameToChannel(name);
|
||||||
|
if (channel != null) {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
throw new ChannelResolutionException(
|
throw new ChannelResolutionException(
|
||||||
"failed to look up MessageChannel bean with name '" + name + "'", e);
|
"failed to look up MessageChannel bean with name '" + name + "'", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,6 +214,10 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
|||||||
this.gateway.setReplyChannel(replyChannel);
|
this.gateway.setReplyChannel(replyChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.getBeanFactory() != null) {
|
||||||
|
this.gateway.setBeanFactory(this.getBeanFactory());
|
||||||
|
}
|
||||||
|
|
||||||
this.gateway.afterPropertiesSet();
|
this.gateway.afterPropertiesSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,6 +298,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
|||||||
* Lifecycle implementation. If no requestChannel is defined, this method
|
* Lifecycle implementation. If no requestChannel is defined, this method
|
||||||
* has no effect as in that case no Gateway is initialized.
|
* has no effect as in that case no Gateway is initialized.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
if (this.gateway != null) {
|
if (this.gateway != null) {
|
||||||
this.gateway.start();
|
this.gateway.start();
|
||||||
@@ -304,6 +309,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
|||||||
* Lifecycle implementation. If no requestChannel is defined, this method
|
* Lifecycle implementation. If no requestChannel is defined, this method
|
||||||
* has no effect as in that case no Gateway is initialized.
|
* has no effect as in that case no Gateway is initialized.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
if (this.gateway != null) {
|
if (this.gateway != null) {
|
||||||
this.gateway.stop();
|
this.gateway.stop();
|
||||||
@@ -314,6 +320,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
|||||||
* Lifecycle implementation. If no requestChannel is defined, this method
|
* Lifecycle implementation. If no requestChannel is defined, this method
|
||||||
* will return always return true as no Gateway is initialized.
|
* will return always return true as no Gateway is initialized.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public boolean isRunning() {
|
public boolean isRunning() {
|
||||||
if (this.gateway != null) {
|
if (this.gateway != null) {
|
||||||
return this.gateway.isRunning();
|
return this.gateway.isRunning();
|
||||||
|
|||||||
@@ -1853,6 +1853,15 @@
|
|||||||
</xsd:documentation>
|
</xsd:documentation>
|
||||||
</xsd:annotation>
|
</xsd:annotation>
|
||||||
</xsd:element>
|
</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:element name="error-channel" type="referenceOrValueHeaderType">
|
||||||
<xsd:annotation>
|
<xsd:annotation>
|
||||||
<xsd:documentation>
|
<xsd:documentation>
|
||||||
|
|||||||
@@ -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,167 @@
|
|||||||
|
/*
|
||||||
|
* 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.Message;
|
||||||
|
import org.springframework.integration.MessageChannel;
|
||||||
|
import org.springframework.integration.channel.DirectChannel;
|
||||||
|
import org.springframework.integration.channel.MessagePublishingErrorHandler;
|
||||||
|
import org.springframework.integration.channel.QueueChannel;
|
||||||
|
import org.springframework.integration.core.MessagingTemplate;
|
||||||
|
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||||
|
import org.springframework.integration.message.ErrorMessage;
|
||||||
|
import org.springframework.integration.message.GenericMessage;
|
||||||
|
import org.springframework.integration.support.MessageBuilder;
|
||||||
|
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() {
|
||||||
|
MessagingTemplate template = new MessagingTemplate();
|
||||||
|
template.setDefaultChannel(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() {
|
||||||
|
MessagingTemplate template = new MessagingTemplate();
|
||||||
|
template.setDefaultChannel(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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
package org.springframework.integration.config.xml;
|
package org.springframework.integration.config.xml;
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -30,6 +30,9 @@ import org.springframework.context.ApplicationContext;
|
|||||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||||
import org.springframework.integration.Message;
|
import org.springframework.integration.Message;
|
||||||
import org.springframework.integration.MessageChannel;
|
import org.springframework.integration.MessageChannel;
|
||||||
|
import org.springframework.integration.channel.DirectChannel;
|
||||||
|
import org.springframework.integration.channel.registry.DefaultHeaderChannelRegistry;
|
||||||
|
import org.springframework.integration.core.MessagingTemplate;
|
||||||
import org.springframework.integration.core.PollableChannel;
|
import org.springframework.integration.core.PollableChannel;
|
||||||
import org.springframework.integration.message.GenericMessage;
|
import org.springframework.integration.message.GenericMessage;
|
||||||
import org.springframework.integration.support.MessageBuilder;
|
import org.springframework.integration.support.MessageBuilder;
|
||||||
@@ -52,6 +55,9 @@ public class ControlBusTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private PollableChannel output;
|
private PollableChannel output;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DefaultHeaderChannelRegistry registry;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDefaultEvaluationContext() {
|
public void testDefaultEvaluationContext() {
|
||||||
Message<?> message = MessageBuilder.withPayload("@service.convert('aardvark')+headers.foo").setHeader("foo", "bar").build();
|
Message<?> message = MessageBuilder.withPayload("@service.convert('aardvark')+headers.foo").setHeader("foo", "bar").build();
|
||||||
@@ -71,6 +77,27 @@ public class ControlBusTests {
|
|||||||
assertNotNull(outputChannel.receive(1000));
|
assertNotNull(outputChannel.receive(1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testControlHeaderChannelReaper() throws InterruptedException {
|
||||||
|
MessagingTemplate messagingTemplate = new MessagingTemplate();
|
||||||
|
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 {
|
public static class Service {
|
||||||
|
|
||||||
@@ -78,12 +105,14 @@ public class ControlBusTests {
|
|||||||
public String convert(String input) {
|
public String convert(String input) {
|
||||||
return "cat";
|
return "cat";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AdapterService {
|
public static class AdapterService {
|
||||||
public Message<String> receive() {
|
public Message<String> receive() {
|
||||||
return new GenericMessage<String>(new Date().toString());
|
return new GenericMessage<String>(new Date().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,17 @@
|
|||||||
<queue />
|
<queue />
|
||||||
</channel>
|
</channel>
|
||||||
|
|
||||||
|
<header-enricher input-channel="requests1" output-channel="requests">
|
||||||
|
<header-channels-to-string />
|
||||||
|
</header-enricher>
|
||||||
|
|
||||||
<channel id="requests"/>
|
<channel id="requests"/>
|
||||||
|
|
||||||
|
<channel id="replies"/>
|
||||||
|
|
||||||
<enricher id="enricher" input-channel="input"
|
<enricher id="enricher" input-channel="input"
|
||||||
request-channel="requests" request-timeout="1234"
|
request-channel="requests1" request-timeout="1234"
|
||||||
reply-timeout="9876"
|
reply-timeout="9876" reply-channel="replies"
|
||||||
order="99" should-clone-payload="true" output-channel="output">
|
order="99" should-clone-payload="true" output-channel="output">
|
||||||
<property name="name" expression="payload.sourceName"/>
|
<property name="name" expression="payload.sourceName"/>
|
||||||
<property name="age" value="42"/>
|
<property name="age" value="42"/>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package org.springframework.integration.config.xml;
|
package org.springframework.integration.config.xml;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNotSame;
|
import static org.junit.Assert.assertNotSame;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
@@ -79,6 +80,7 @@ public class EnricherParserTests {
|
|||||||
assertEquals(context.getBean("output"), accessor.getPropertyValue("outputChannel"));
|
assertEquals(context.getBean("output"), accessor.getPropertyValue("outputChannel"));
|
||||||
assertEquals(true, accessor.getPropertyValue("shouldClonePayload"));
|
assertEquals(true, accessor.getPropertyValue("shouldClonePayload"));
|
||||||
assertNull(accessor.getPropertyValue("requestPayloadExpression"));
|
assertNull(accessor.getPropertyValue("requestPayloadExpression"));
|
||||||
|
assertNotNull(TestUtils.getPropertyValue(enricher, "gateway.beanFactory"));
|
||||||
|
|
||||||
Map<Expression, Expression> propertyExpressions = (Map<Expression, Expression>) accessor.getPropertyValue("propertyExpressions");
|
Map<Expression, Expression> propertyExpressions = (Map<Expression, Expression>) accessor.getPropertyValue("propertyExpressions");
|
||||||
for (Map.Entry<Expression, Expression> e : propertyExpressions.entrySet()) {
|
for (Map.Entry<Expression, Expression> e : propertyExpressions.entrySet()) {
|
||||||
@@ -125,12 +127,15 @@ public class EnricherParserTests {
|
|||||||
@Test
|
@Test
|
||||||
public void integrationTest() {
|
public void integrationTest() {
|
||||||
SubscribableChannel requests = context.getBean("requests", SubscribableChannel.class);
|
SubscribableChannel requests = context.getBean("requests", SubscribableChannel.class);
|
||||||
requests.subscribe(new AbstractReplyProducingMessageHandler() {
|
class Foo extends AbstractReplyProducingMessageHandler {
|
||||||
@Override
|
@Override
|
||||||
protected Object handleRequestMessage(Message<?> requestMessage) {
|
protected Object handleRequestMessage(Message<?> requestMessage) {
|
||||||
return new Source("foo");
|
return new Source("foo");
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
Foo foo = new Foo();
|
||||||
|
foo.setOutputChannel(context.getBean("replies", MessageChannel.class));
|
||||||
|
requests.subscribe(foo);
|
||||||
Target original = new Target();
|
Target original = new Target();
|
||||||
Message<?> request = MessageBuilder.withPayload(original)
|
Message<?> request = MessageBuilder.withPayload(original)
|
||||||
.setHeader("sourceName", "test")
|
.setHeader("sourceName", "test")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2011 the original author or authors.
|
* Copyright 2002-2013 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -28,6 +28,9 @@ import javax.jms.Session;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
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.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.integration.Message;
|
import org.springframework.integration.Message;
|
||||||
import org.springframework.integration.MessageChannel;
|
import org.springframework.integration.MessageChannel;
|
||||||
@@ -53,7 +56,7 @@ import org.springframework.util.Assert;
|
|||||||
* @author Oleg Zhurakousky
|
* @author Oleg Zhurakousky
|
||||||
*/
|
*/
|
||||||
public class ChannelPublishingJmsMessageListener
|
public class ChannelPublishingJmsMessageListener
|
||||||
implements SessionAwareMessageListener<javax.jms.Message>, InitializingBean, TrackableComponent {
|
implements SessionAwareMessageListener<javax.jms.Message>, InitializingBean, TrackableComponent, BeanFactoryAware {
|
||||||
|
|
||||||
protected final Log logger = LogFactory.getLog(getClass());
|
protected final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
@@ -83,6 +86,8 @@ public class ChannelPublishingJmsMessageListener
|
|||||||
|
|
||||||
private final GatewayDelegate gatewayDelegate = new GatewayDelegate();
|
private final GatewayDelegate gatewayDelegate = new GatewayDelegate();
|
||||||
|
|
||||||
|
private volatile BeanFactory beanFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify whether a JMS reply Message is expected.
|
* Specify whether a JMS reply Message is expected.
|
||||||
*/
|
*/
|
||||||
@@ -114,14 +119,17 @@ public class ChannelPublishingJmsMessageListener
|
|||||||
this.gatewayDelegate.setReplyTimeout(replyTimeout);
|
this.gatewayDelegate.setReplyTimeout(replyTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setShouldTrack(boolean shouldTrack) {
|
public void setShouldTrack(boolean shouldTrack) {
|
||||||
this.gatewayDelegate.setShouldTrack(shouldTrack);
|
this.gatewayDelegate.setShouldTrack(shouldTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getComponentName() {
|
public String getComponentName() {
|
||||||
return this.gatewayDelegate.getComponentName();
|
return this.gatewayDelegate.getComponentName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getComponentType() {
|
public String getComponentType() {
|
||||||
return this.gatewayDelegate.getComponentType();
|
return this.gatewayDelegate.getComponentType();
|
||||||
}
|
}
|
||||||
@@ -260,6 +268,12 @@ public class ChannelPublishingJmsMessageListener
|
|||||||
this.extractReplyPayload = extractReplyPayload;
|
this.extractReplyPayload = extractReplyPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||||
|
this.beanFactory = beanFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onMessage(javax.jms.Message jmsMessage, Session session) throws JMSException {
|
public void onMessage(javax.jms.Message jmsMessage, Session session) throws JMSException {
|
||||||
Object result = jmsMessage;
|
Object result = jmsMessage;
|
||||||
if (this.extractRequestPayload) {
|
if (this.extractRequestPayload) {
|
||||||
@@ -305,7 +319,11 @@ public class ChannelPublishingJmsMessageListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void afterPropertiesSet() {
|
public void afterPropertiesSet() {
|
||||||
|
if (this.beanFactory != null) {
|
||||||
|
this.gatewayDelegate.setBeanFactory(this.beanFactory);
|
||||||
|
}
|
||||||
this.gatewayDelegate.afterPropertiesSet();
|
this.gatewayDelegate.afterPropertiesSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,14 +437,17 @@ public class ChannelPublishingJmsMessageListener
|
|||||||
|
|
||||||
private class GatewayDelegate extends MessagingGatewaySupport {
|
private class GatewayDelegate extends MessagingGatewaySupport {
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void send(Object request) {
|
protected void send(Object request) {
|
||||||
super.send(request);
|
super.send(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected Message<?> sendAndReceiveMessage(Object request) {
|
protected Message<?> sendAndReceiveMessage(Object request) {
|
||||||
return super.sendAndReceiveMessage(request);
|
return super.sendAndReceiveMessage(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getComponentType() {
|
public String getComponentType() {
|
||||||
if (expectReply) {
|
if (expectReply) {
|
||||||
return "jms:inbound-gateway";
|
return "jms:inbound-gateway";
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?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"
|
||||||
|
xmlns:int-jms="http://www.springframework.org/schema/integration/jms"
|
||||||
|
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
|
||||||
|
http://www.springframework.org/schema/integration/jms http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd">
|
||||||
|
|
||||||
|
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
|
||||||
|
<property name="brokerURL" value="vm://localhost"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<int:channel id="input" />
|
||||||
|
|
||||||
|
<int:channel id="output">
|
||||||
|
<int:queue />
|
||||||
|
</int:channel>
|
||||||
|
|
||||||
|
<int-jms:outbound-gateway request-channel="input" reply-channel="output"
|
||||||
|
connection-factory="connectionFactory"
|
||||||
|
request-destination-name="serialized.reply.channel" />
|
||||||
|
|
||||||
|
<int-jms:inbound-gateway request-channel="foo" reply-channel="baz"
|
||||||
|
request-destination-name="serialized.reply.channel"
|
||||||
|
connection-factory="connectionFactory"/>
|
||||||
|
|
||||||
|
<int:header-enricher input-channel="foo" output-channel="bar">
|
||||||
|
<int:header-channels-to-string />
|
||||||
|
</int:header-enricher>
|
||||||
|
|
||||||
|
<int:transformer input-channel="bar" output-channel="baz"
|
||||||
|
expression="'echo:' + payload" />
|
||||||
|
|
||||||
|
<int:channel id="baz" />
|
||||||
|
|
||||||
|
</beans>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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.jms.request_reply;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.integration.Message;
|
||||||
|
import org.springframework.integration.MessageChannel;
|
||||||
|
import org.springframework.integration.core.PollableChannel;
|
||||||
|
import org.springframework.integration.message.GenericMessage;
|
||||||
|
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 GatewaySerializedReplyChannelTests {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MessageChannel input;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
PollableChannel output;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
input.send(new GenericMessage<String>("foo"));
|
||||||
|
Message<?> reply = output.receive(0);
|
||||||
|
assertNotNull(reply);
|
||||||
|
assertEquals("echo:foo", reply.getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -73,10 +73,10 @@
|
|||||||
using generic <emphasis><header></emphasis> sub-elements where
|
using generic <emphasis><header></emphasis> sub-elements where
|
||||||
you would have to provide both header 'name' and 'value', you can use
|
you would have to provide both header 'name' and 'value', you can use
|
||||||
convenient sub-elements to set those values directly.
|
convenient sub-elements to set those values directly.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
<emphasis>POJO Support</emphasis>
|
<emphasis role="bold">POJO Support</emphasis>
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
</int:header-enricher>]]></programlisting>
|
</int:header-enricher>]]></programlisting>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
<emphasis>SpEL Support</emphasis>
|
<emphasis role="bold">SpEL Support</emphasis>
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
In Spring Integration 2.0 we have introduced the convenience of the
|
In Spring Integration 2.0 we have introduced the convenience of the
|
||||||
@@ -147,6 +147,51 @@
|
|||||||
are bound to the SpEL Evaluation Context, giving you full access to
|
are bound to the SpEL Evaluation Context, giving you full access to
|
||||||
the incoming Message.
|
the incoming Message.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para><emphasis role="bold">Header Channel Registry</emphasis></para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Starting with <emphasis>Spring Integration 3.0</emphasis>, a new sub-element
|
||||||
|
<code><int:header-channels-to-string/></code> is available; it has no attributes.
|
||||||
|
This converts existing <code>replyChannel</code> and <code>errorChannel</code>
|
||||||
|
headers (when they are a
|
||||||
|
<classname>MessageChannel</classname>) to a String and stores the channel(s) in
|
||||||
|
a registry for later resolution when it is time to send a reply, or handle an error.
|
||||||
|
This is useful
|
||||||
|
for cases where the headers might be lost; for example when
|
||||||
|
serializing a message into a message store or when transporting the message
|
||||||
|
over JMS. If the header does not already exist, or it
|
||||||
|
is not a <classname>MessageChannel</classname>, no changes are made.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Use of this functionality requires the presence of a <classname>HeaderChannelRegistry</classname>
|
||||||
|
bean. By default, the framework creates a <classname>DefaultHeaderChannelRegistry</classname>
|
||||||
|
with the default expiry (60 seconds). Channels
|
||||||
|
are removed from the registry after this time. To change this, simply define a bean
|
||||||
|
with id <code>integrationHeaderChannelRegistry</code> and configure the required delay using
|
||||||
|
a constructor argument (milliseconds).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <classname>HeaderChannelRegistry</classname> has a <code>size()</code> method to
|
||||||
|
determine the current size of the registry. The <code>runReaper()</code> method
|
||||||
|
cancels the current scheduled task and runs the reaper immediately; the task is
|
||||||
|
then scheduled to run again based on the current delay. These methods can be invoked
|
||||||
|
directly by getting a reference to the registry, or you can send a message with, for example,
|
||||||
|
the following content to a control bus:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA["@integrationHeaderChannelRegistry.runReaper()"]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This sub-element is a convenience only, and is the equivalent of specifying:
|
||||||
|
</para>
|
||||||
|
<programlisting language="xml"><![CDATA[<int:reply-channel
|
||||||
|
expression="@integrationHeaderChannelRegistry.channelToChannelName(headers.replyChannel)"/>
|
||||||
|
<int:error-channel
|
||||||
|
expression="@integrationHeaderChannelRegistry.channelToChannelName(headers.errorChannel)"/>]]></programlisting>
|
||||||
|
|
||||||
<tip>
|
<tip>
|
||||||
For more examples for configuring header enrichers, see
|
For more examples for configuring header enrichers, see
|
||||||
<ulink url="https://github.com/SpringSource/spring-integration/wiki/Header-Enricher-Advanced-Configuration">
|
<ulink url="https://github.com/SpringSource/spring-integration/wiki/Header-Enricher-Advanced-Configuration">
|
||||||
|
|||||||
@@ -75,21 +75,21 @@
|
|||||||
For example, if one of the headers contains an instance of some <emphasis>Spring Bean</emphasis>, upon deserialization you may end
|
For example, if one of the headers contains an instance of some <emphasis>Spring Bean</emphasis>, upon deserialization you may end
|
||||||
up with a different instance of that bean,
|
up with a different instance of that bean,
|
||||||
which directly affects some of the implicit headers created by the framework (e.g., REPLY_CHANNEL or ERROR_CHANNEL).
|
which directly affects some of the implicit headers created by the framework (e.g., REPLY_CHANNEL or ERROR_CHANNEL).
|
||||||
Currently they are not serializable, but even if they were the deserialized channel would not represent the expected instance.
|
Currently they are not serializable, but even if they were, the deserialized channel would not represent the expected instance.
|
||||||
As a workaround we suggest to remove bean-ref headers via a <literal><header-filter/></literal>
|
|
||||||
before sending a message to an endpoint backed by a persistent <classname>MessageStore</classname>.
|
|
||||||
Also, we recommend using channel names instead of channel instances when setting those types of headers,
|
|
||||||
thus allowing it to be resolved in real time by the <classname>ChannelResolver</classname>.
|
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
Also avoid configuration of a message-flow like this:
|
Beginning with <emphasis>Spring Integration version 3.0</emphasis>, this issue can be resolved with a header enricher,
|
||||||
|
configured to replace these headers with a name after registering the channel with the <classname>HeaderChannelRegistry</classname>.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Also when configuring a message-flow like this:
|
||||||
<emphasis>gateway -> queue-channel (backed by a persistent Message Store) -> service-activator</emphasis>
|
<emphasis>gateway -> queue-channel (backed by a persistent Message Store) -> service-activator</emphasis>
|
||||||
That gateway creates a <emphasis>Temporary Reply Channel</emphasis> in the background, and it will be lost by the time the
|
That gateway creates a <emphasis>Temporary Reply Channel</emphasis>, and it will be lost by the time the
|
||||||
service-activator's poller reads from the queue, because it has been deserialized by another thread on the sending side.
|
service-activator's poller reads from the queue. Again, you can use the header enricher to replace the headers with a
|
||||||
|
String representation.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
Nevertheless we are constantly thinking about potential improvements to the framework, such as a way to provide some
|
For more information, refer to the <xref linkend="header-enricher"/>.
|
||||||
robust default serialization strategy for messages in these cases.
|
|
||||||
</para>
|
</para>
|
||||||
</important>
|
</important>
|
||||||
</para>
|
</para>
|
||||||
|
|||||||
@@ -166,6 +166,16 @@
|
|||||||
For more information see <xref linkend="redis" />.
|
For more information see <xref linkend="redis" />.
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</section>
|
||||||
|
<section id="3.0-hcr">
|
||||||
|
<title>Header Channel Registry</title>
|
||||||
|
<para>
|
||||||
|
It is now possible to instruct the framework to store reply and error channels
|
||||||
|
in a registry for later resolution. This is useful for cases where
|
||||||
|
the <code>replyChannel</code> or <code>errorChannel</code> might be lost; for example
|
||||||
|
when serializing
|
||||||
|
a message. See <xref linkend="header-enricher"/> for more information.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="3.0-general">
|
<section id="3.0-general">
|
||||||
|
|||||||
Reference in New Issue
Block a user