diff --git a/build.gradle b/build.gradle index 5400b0aa2a..4a22098623 100644 --- a/build.gradle +++ b/build.gradle @@ -56,7 +56,6 @@ subprojects { subproject -> junitVersion = '4.11' log4jVersion = '1.2.12' mockitoVersion = '1.9.5' - eaioUUIDVersion = '3.2' ftpServerVersion = '1.0.6' @@ -195,7 +194,6 @@ project('spring-integration-core') { compile "org.springframework:spring-messaging:$springVersion" compile "org.springframework:spring-tx:$springVersion" compile "org.springframework.retry:spring-retry:$springRetryVersion" - compile "com.eaio.uuid:uuid:$eaioUUIDVersion" compile("org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion", optional) compile("com.fasterxml.jackson.core:jackson-databind:$jackson2Version", optional) compile('com.jayway.jsonpath:json-path:0.8.1', optional) @@ -644,6 +642,7 @@ task api(type: Javadoc) { options.author = true options.header = rootProject.description options.overview = 'src/api/overview.html' + options.stylesheetFile = file("src/api/stylesheet.css") source subprojects.collect { project -> project.sourceSets.main.allJava } diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.java index 1aba0d5e60..47ab1f1131 100644 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.java +++ b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.java @@ -26,8 +26,8 @@ import org.springframework.amqp.core.MessageDeliveryMode; import org.springframework.amqp.core.MessageProperties; import org.springframework.integration.EiMessageHeaderAccessor; import org.springframework.integration.amqp.AmqpHeaders; -import org.springframework.integration.json.JsonHeaders; import org.springframework.integration.mapping.AbstractHeaderMapper; +import org.springframework.integration.mapping.support.JsonHeaders; import org.springframework.util.StringUtils; /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/MessageHeaders.java b/spring-integration-core/src/main/java/org/springframework/integration/MessageHeaders.java new file mode 100644 index 0000000000..823250c98a --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/MessageHeaders.java @@ -0,0 +1,336 @@ +/* + * 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. + * 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; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The headers for a {@link Message}.
+ * IMPORTANT: MessageHeaders are immutable. Any mutating operation (e.g., put(..), putAll(..) etc.) + * will result in {@link UnsupportedOperationException} + * To create MessageHeaders instance use fluent MessageBuilder API + *
+ * MessageBuilder.withPayload("foo").setHeader("key1", "value1").setHeader("key2", "value2");
+ * 
+ * or create an instance of GenericMessage passing payload as {@link Object} and headers as a regular {@link Map} + *
+ * Map headers = new HashMap();
+ * headers.put("key1", "value1");
+ * headers.put("key2", "value2");
+ * new GenericMessage("foo", headers);
+ * 
+ * + * @author Arjen Poutsma + * @author Mark Fisher + * @author Oleg Zhurakousky + * @author Gary Russell + * @author Rossen Stoyanchev + */ +public final class MessageHeaders implements Map, Serializable { + + private static final long serialVersionUID = 6901029029524535147L; + + private static final Log logger = LogFactory.getLog(MessageHeaders.class); + + private static volatile IdGenerator idGenerator = null; + + private static final IdGenerator defaultIdGenerator = new AlternativeJdkIdGenerator(); + + /** + * The key for the Message ID. This is an automatically generated UUID and + * should never be explicitly set in the header map except in the + * case of Message deserialization where the serialized Message's generated + * UUID is being restored. + */ + public static final String ID = "id"; + + public static final String TIMESTAMP = "timestamp"; + + public static final String CORRELATION_ID = "correlationId"; + + public static final String REPLY_CHANNEL = "replyChannel"; + + public static final String ERROR_CHANNEL = "errorChannel"; + + public static final String EXPIRATION_DATE = "expirationDate"; + + public static final String PRIORITY = "priority"; + + public static final String SEQUENCE_NUMBER = "sequenceNumber"; + + public static final String SEQUENCE_SIZE = "sequenceSize"; + + public static final String SEQUENCE_DETAILS = "sequenceDetails"; + + public static final String CONTENT_TYPE = "content-type"; + + public static final String POSTPROCESS_RESULT = "postProcessResult"; + + + private final Map headers; + + + public MessageHeaders(Map headers) { + this.headers = (headers != null) ? new HashMap(headers) : new HashMap(); + IdGenerator generatorToUse = (idGenerator != null) ? idGenerator : defaultIdGenerator; + this.headers.put(ID, generatorToUse.generateId()); + + this.headers.put(TIMESTAMP, new Long(System.currentTimeMillis())); + } + + public UUID getId() { + return this.get(ID, UUID.class); + } + + public Long getTimestamp() { + return this.get(TIMESTAMP, Long.class); + } + + public Long getExpirationDate() { + return this.get(EXPIRATION_DATE, Long.class); + } + + public Object getCorrelationId() { + return this.get(CORRELATION_ID); + } + + public Object getReplyChannel() { + return this.get(REPLY_CHANNEL); + } + + public Object getErrorChannel() { + return this.get(ERROR_CHANNEL); + } + + public Integer getSequenceNumber() { + Integer sequenceNumber = this.get(SEQUENCE_NUMBER, Integer.class); + return (sequenceNumber != null ? sequenceNumber : 0); + } + + public Integer getSequenceSize() { + Integer sequenceSize = this.get(SEQUENCE_SIZE, Integer.class); + return (sequenceSize != null ? sequenceSize : 0); + } + + public Integer getPriority() { + return this.get(PRIORITY, Integer.class); + } + + @SuppressWarnings("unchecked") + public T get(Object key, Class type) { + Object value = this.headers.get(key); + if (value == null) { + return null; + } + if (!type.isAssignableFrom(value.getClass())) { + throw new IllegalArgumentException("Incorrect type specified for header '" + key + "'. Expected [" + type + + "] but actual type is [" + value.getClass() + "]"); + } + return (T) value; + } + + @Override + public int hashCode() { + return this.headers.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object != null && object instanceof MessageHeaders) { + MessageHeaders other = (MessageHeaders) object; + return this.headers.equals(other.headers); + } + return false; + } + + @Override + public String toString() { + return this.headers.toString(); + } + + /* + * Map implementation + */ + + public boolean containsKey(Object key) { + return this.headers.containsKey(key); + } + + public boolean containsValue(Object value) { + return this.headers.containsValue(value); + } + + public Set> entrySet() { + return Collections.unmodifiableSet(this.headers.entrySet()); + } + + public Object get(Object key) { + return this.headers.get(key); + } + + public boolean isEmpty() { + return this.headers.isEmpty(); + } + + public Set keySet() { + return Collections.unmodifiableSet(this.headers.keySet()); + } + + public int size() { + return this.headers.size(); + } + + public Collection values() { + return Collections.unmodifiableCollection(this.headers.values()); + } + + /* + * Unsupported operations + */ + /** + * Since MessageHeaders are immutable the call to this method will result in {@link UnsupportedOperationException} + */ + public Object put(String key, Object value) { + throw new UnsupportedOperationException("MessageHeaders is immutable."); + } + /** + * Since MessageHeaders are immutable the call to this method will result in {@link UnsupportedOperationException} + */ + public void putAll(Map t) { + throw new UnsupportedOperationException("MessageHeaders is immutable."); + } + /** + * Since MessageHeaders are immutable the call to this method will result in {@link UnsupportedOperationException} + */ + public Object remove(Object key) { + throw new UnsupportedOperationException("MessageHeaders is immutable."); + } + /** + * Since MessageHeaders are immutable the call to this method will result in {@link UnsupportedOperationException} + */ + public void clear() { + throw new UnsupportedOperationException("MessageHeaders is immutable."); + } + + /* + * Serialization methods + */ + + private void writeObject(ObjectOutputStream out) throws IOException { + List keysToRemove = new ArrayList(); + for (Map.Entry entry : this.headers.entrySet()) { + if (!(entry.getValue() instanceof Serializable)) { + keysToRemove.add(entry.getKey()); + } + } + for (String key : keysToRemove) { + if (logger.isInfoEnabled()) { + logger.info("removing non-serializable header: " + key); + } + this.headers.remove(key); + } + out.defaultWriteObject(); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + } + + public static interface IdGenerator { + UUID generateId(); + } + + public static class JdkIdGenerator implements IdGenerator { + + @Override + public UUID generateId() { + return UUID.randomUUID(); + } + + } + + /** + * A variation of {@link UUID#randomUUID()} that uses {@link SecureRandom} only for + * the initial seed and {@link Random} thereafter, which provides better performance + * in exchange for less securely random id's. + */ + public static class AlternativeJdkIdGenerator implements IdGenerator { + + private final Random random; + + public AlternativeJdkIdGenerator() { + byte[] seed = new SecureRandom().generateSeed(8); + this.random = new Random(new BigInteger(seed).longValue()); + } + + public UUID generateId() { + + byte[] randomBytes = new byte[16]; + this.random.nextBytes(randomBytes); + + long mostSigBits = 0; + for (int i = 0; i < 8; i++) { + mostSigBits = (mostSigBits << 8) | (randomBytes[i] & 0xff); + } + long leastSigBits = 0; + for (int i = 8; i < 16; i++) { + leastSigBits = (leastSigBits << 8) | (randomBytes[i] & 0xff); + } + + return new UUID(mostSigBits, leastSigBits); + } + } + + public static class SimpleIncrementingIdGenerator implements IdGenerator { + + private final AtomicLong topBits = new AtomicLong(); + + private final AtomicLong bottomBits = new AtomicLong(); + + @Override + public UUID generateId() { + long bottomBits = this.bottomBits.incrementAndGet(); + if (bottomBits == 0) { + this.topBits.incrementAndGet(); + } + return new UUID(this.topBits.get(), bottomBits); + } + + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractPollingInboundChannelAdapterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractPollingInboundChannelAdapterParser.java index 7a672c9297..6da6aec529 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractPollingInboundChannelAdapterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractPollingInboundChannelAdapterParser.java @@ -19,6 +19,7 @@ package org.springframework.integration.config.xml; import org.w3c.dom.Element; import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; @@ -31,18 +32,25 @@ import org.springframework.util.xml.DomUtils; * @author Mark Fisher * @author Gary Russell * @author Oleg Zhurakousky + * @author Artem Bilan */ 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) { parserContext.getReaderContext().error("failed to parse source", element); } + + String channelAdapterId = this.resolveId(element, (AbstractBeanDefinition) source, parserContext); + String sourceBeanName = channelAdapterId + ".source"; + parserContext.getRegistry().registerBeanDefinition(sourceBeanName, (BeanDefinition) source); + BeanDefinitionBuilder adapterBuilder = BeanDefinitionBuilder .genericBeanDefinition(SourcePollingChannelAdapterFactoryBean.class); - adapterBuilder.addPropertyValue("source", source); + adapterBuilder.addPropertyReference("source", sourceBeanName); adapterBuilder.addPropertyReference("outputChannel", channelName); IntegrationNamespaceUtils.setValueIfAttributeDefined(adapterBuilder, element, "send-timeout"); Element pollerElement = DomUtils.getChildElementByTagName(element, "poller"); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java index 150a3dc054..df6d2f0012 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java @@ -172,9 +172,19 @@ public abstract class IntegrationNamespaceUtils { */ public static void setReferenceIfAttributeDefined(BeanDefinitionBuilder builder, Element element, String attributeName, String propertyName) { - String attributeValue = element.getAttribute(attributeName); - if (StringUtils.hasText(attributeValue)) { - builder.addPropertyReference(propertyName, attributeValue); + setReferenceIfAttributeDefined(builder, element, attributeName, propertyName, false); + } + + public static void setReferenceIfAttributeDefined(BeanDefinitionBuilder builder, Element element, + String attributeName, String propertyName, boolean emptyStringAllowed) { + if (element.hasAttribute(attributeName)) { + String attributeValue = element.getAttribute(attributeName); + if (StringUtils.hasText(attributeValue)) { + builder.addPropertyReference(propertyName, attributeValue); + } + else if (emptyStringAllowed) { + builder.addPropertyValue(propertyName, null); + } } } @@ -198,8 +208,13 @@ public abstract class IntegrationNamespaceUtils { */ public static void setReferenceIfAttributeDefined(BeanDefinitionBuilder builder, Element element, String attributeName) { + setReferenceIfAttributeDefined(builder, element, attributeName, false); + } + + public static void setReferenceIfAttributeDefined(BeanDefinitionBuilder builder, Element element, + String attributeName, boolean emptyStringAllowed) { setReferenceIfAttributeDefined(builder, element, attributeName, - Conventions.attributeNameToPropertyName(attributeName)); + Conventions.attributeNameToPropertyName(attributeName), emptyStringAllowed); } /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ObjectToMapTransformerParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ObjectToMapTransformerParser.java index 6226f63cfa..501ffaf4d4 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ObjectToMapTransformerParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ObjectToMapTransformerParser.java @@ -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. @@ -22,6 +22,7 @@ import org.w3c.dom.Element; /** * @author Oleg Zhurakousky + * @author Mauro Franceschini * @since 2.0 */ public class ObjectToMapTransformerParser extends AbstractTransformerParser { @@ -33,5 +34,6 @@ public class ObjectToMapTransformerParser extends AbstractTransformerParser { @Override protected void parseTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "flatten", "shouldFlattenKeys"); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java index 9e80a68860..381a8a896f 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java @@ -19,7 +19,7 @@ package org.springframework.integration.context; import org.springframework.beans.factory.BeanFactory; import org.springframework.core.convert.ConversionService; import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.integration.store.metadata.MetadataStore; +import org.springframework.integration.metadata.MetadataStore; import org.springframework.messaging.MessageChannel; import org.springframework.scheduling.TaskScheduler; import org.springframework.util.Assert; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandlerChain.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandlerChain.java index 5f7d7bea7a..9a8a602912 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandlerChain.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/MessageHandlerChain.java @@ -52,13 +52,15 @@ import org.springframework.util.Assert; * the configuration by removing channels that can be created implicitly. *

* - *

- * <chain>
- *     <filter ref="someFilter"/>
- *     <bean class="SomeMessageHandlerImplementation"/>
- *     <transformer ref="someTransformer"/>
- *     <aggregator ... />
- * </chain>
+ * 
+ * {@code
+ * 
+ *     
+ *     
+ *     
+ *     
+ * 
+ * }
  * 
* * @author Mark Fisher diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java index 7669b45bb5..3f6395b7ad 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java @@ -17,6 +17,7 @@ package org.springframework.integration.json; import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.integration.mapping.support.JsonHeaders; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.json.JacksonJsonObjectMapper; import org.springframework.integration.support.json.JacksonJsonObjectMapperProvider; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/mapping/AbstractHeaderMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/mapping/AbstractHeaderMapper.java index 82d8ddc70b..2e2c7430ea 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/mapping/AbstractHeaderMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/mapping/AbstractHeaderMapper.java @@ -27,7 +27,7 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.integration.json.JsonHeaders; +import org.springframework.integration.mapping.support.JsonHeaders; import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonHeaders.java b/spring-integration-core/src/main/java/org/springframework/integration/mapping/support/JsonHeaders.java similarity index 95% rename from spring-integration-core/src/main/java/org/springframework/integration/json/JsonHeaders.java rename to spring-integration-core/src/main/java/org/springframework/integration/mapping/support/JsonHeaders.java index 5381bdf472..fa2c88eef2 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonHeaders.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/mapping/support/JsonHeaders.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.json; +package org.springframework.integration.mapping.support; import java.util.Arrays; import java.util.Collection; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/mapping/support/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/mapping/support/package-info.java new file mode 100644 index 0000000000..0fa87a6375 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/mapping/support/package-info.java @@ -0,0 +1,4 @@ +/** + * Support classes for mapping. + */ +package org.springframework.integration.mapping.support; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/store/metadata/MetadataStore.java b/spring-integration-core/src/main/java/org/springframework/integration/metadata/MetadataStore.java similarity index 69% rename from spring-integration-core/src/main/java/org/springframework/integration/store/metadata/MetadataStore.java rename to spring-integration-core/src/main/java/org/springframework/integration/metadata/MetadataStore.java index de4a8376aa..06d48c9cab 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/store/metadata/MetadataStore.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/metadata/MetadataStore.java @@ -14,7 +14,10 @@ * limitations under the License. */ -package org.springframework.integration.store.metadata; +package org.springframework.integration.metadata; + +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedResource; /** * Strategy interface for storing metadata from certain adapters @@ -25,6 +28,7 @@ package org.springframework.integration.store.metadata; * @author Mark Fisher * @since 2.0 */ +@ManagedResource public interface MetadataStore { /** @@ -35,6 +39,15 @@ public interface MetadataStore { /** * Reads a value for the given key from this MetadataStore. */ + @ManagedAttribute String get(String key); + /** + * Remove a value for the given key from this MetadataStore. + * return the previous value associated with key, or + * null if there was no mapping for key. + */ + @ManagedAttribute + String remove(String key); + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/store/metadata/PropertiesPersistingMetadataStore.java b/spring-integration-core/src/main/java/org/springframework/integration/metadata/PropertiesPersistingMetadataStore.java similarity index 96% rename from spring-integration-core/src/main/java/org/springframework/integration/store/metadata/PropertiesPersistingMetadataStore.java rename to spring-integration-core/src/main/java/org/springframework/integration/metadata/PropertiesPersistingMetadataStore.java index 3393468cfd..26c35231ec 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/store/metadata/PropertiesPersistingMetadataStore.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/metadata/PropertiesPersistingMetadataStore.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.store.metadata; +package org.springframework.integration.metadata; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -86,6 +86,12 @@ public class PropertiesPersistingMetadataStore implements MetadataStore, Initial return this.metadata.getProperty(key); } + @Override + @SuppressWarnings("uchecked") + public String remove(String key) { + return (String) this.metadata.remove(key); + } + public void destroy() throws Exception { this.saveMetadata(); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/store/metadata/SimpleMetadataStore.java b/spring-integration-core/src/main/java/org/springframework/integration/metadata/SimpleMetadataStore.java similarity index 89% rename from spring-integration-core/src/main/java/org/springframework/integration/store/metadata/SimpleMetadataStore.java rename to spring-integration-core/src/main/java/org/springframework/integration/metadata/SimpleMetadataStore.java index 5a04477715..0bf5b50fdd 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/store/metadata/SimpleMetadataStore.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/metadata/SimpleMetadataStore.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package org.springframework.integration.store.metadata; +package org.springframework.integration.metadata; import java.util.HashMap; import java.util.Map; @@ -37,4 +37,9 @@ public class SimpleMetadataStore implements MetadataStore { return this.metadata.get(key); } + @Override + public String remove(String key) { + return metadata.remove(key); + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/store/metadata/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/metadata/package-info.java similarity index 50% rename from spring-integration-core/src/main/java/org/springframework/integration/store/metadata/package-info.java rename to spring-integration-core/src/main/java/org/springframework/integration/metadata/package-info.java index 0cfc461d34..d27403d529 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/store/metadata/package-info.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/metadata/package-info.java @@ -1,4 +1,4 @@ /** * Provides classes supporting metadata stores. */ -package org.springframework.integration.store.metadata; +package org.springframework.integration.metadata; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java index 06b523acbf..8d77b8016b 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java @@ -27,11 +27,13 @@ import org.springframework.integration.core.MessageSelector; import org.springframework.util.Assert; /** - *
- * <recipient-list-router id="simpleRouter" input-channel="routingChannelA">
- *     <recipient channel="channel1"/>
- *     <recipient channel="channel2"/>
- * </recipient-list-router>
+ * 
+ * {@code
+ * 
+ *     
+ *     
+ * 
+ * }
  * 
*

* A Message Router that sends Messages to a list of recipient channels. The diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/json/Jackson2JsonObjectMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/support/json/Jackson2JsonObjectMapper.java index 0d802e5bc4..30a6b28a89 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/json/Jackson2JsonObjectMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/json/Jackson2JsonObjectMapper.java @@ -25,7 +25,7 @@ import java.net.URL; import java.util.Collection; import java.util.Map; -import org.springframework.integration.json.JsonHeaders; +import org.springframework.integration.mapping.support.JsonHeaders; import org.springframework.util.Assert; import com.fasterxml.jackson.core.JsonParser; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/json/JacksonJsonObjectMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/support/json/JacksonJsonObjectMapper.java index 5297a2bb33..92342dd621 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/json/JacksonJsonObjectMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/json/JacksonJsonObjectMapper.java @@ -29,7 +29,7 @@ import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.type.JavaType; -import org.springframework.integration.json.JsonHeaders; +import org.springframework.integration.mapping.support.JsonHeaders; import org.springframework.util.Assert; /** diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd index 00dc093ed1..9343147740 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd @@ -2169,6 +2169,15 @@ + + + + Specifies if the result Map of Maps should be transformed further to flat keys of + object's property paths. + Default is 'true'. + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ObjectToMapTransformerParserTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ObjectToMapTransformerParserTests-context.xml index c0dfd12e5d..11ef5d780d 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ObjectToMapTransformerParserTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ObjectToMapTransformerParserTests-context.xml @@ -15,4 +15,12 @@ + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ObjectToMapTransformerParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ObjectToMapTransformerParserTests.java index db49eb9067..6ffcf58cac 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ObjectToMapTransformerParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ObjectToMapTransformerParserTests.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -40,10 +41,12 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; /** * @author Oleg Zhurakousky * @author Gunnar Hillert + * @author Mauro Franceschini */ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) @@ -57,6 +60,14 @@ public class ObjectToMapTransformerParserTests { @Qualifier("output") private PollableChannel output; + @Autowired + @Qualifier("nestedInput") + private MessageChannel nestedInput; + + @Autowired + @Qualifier("nestedOutput") + private PollableChannel nestedOutput; + @SuppressWarnings("unchecked") @Test @@ -90,6 +101,23 @@ public class ObjectToMapTransformerParserTests { directInput.send(message); } + @Test + public void testObjectToNotFlattenedMapTransformer(){ + Employee employee = this.buildEmployee(); + + Message message = MessageBuilder.withPayload(employee).build(); + nestedInput.send(message); + + @SuppressWarnings("unchecked") + Message> outputMessage = (Message>) nestedOutput.receive(1000); + Map transformedMap = outputMessage.getPayload(); + assertNotNull(outputMessage.getPayload()); + + assertEquals(employee.getCompanyName(), transformedMap.get("companyName")); + assertThat(transformedMap.get("companyAddress"), Matchers.instanceOf(Map.class)); + assertThat(transformedMap.get("departments"), Matchers.instanceOf(List.class)); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) public Employee buildEmployee(){ Address companyAddress = new Address(); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/store/metadata/PropertiesPersistingMetadataStoreTests.java b/spring-integration-core/src/test/java/org/springframework/integration/metadata/PropertiesPersistingMetadataStoreTests.java similarity index 94% rename from spring-integration-core/src/test/java/org/springframework/integration/store/metadata/PropertiesPersistingMetadataStoreTests.java rename to spring-integration-core/src/test/java/org/springframework/integration/metadata/PropertiesPersistingMetadataStoreTests.java index f3ac8a7748..8ed8cc371c 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/store/metadata/PropertiesPersistingMetadataStoreTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/metadata/PropertiesPersistingMetadataStoreTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.store.metadata; +package org.springframework.integration.metadata; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -27,7 +27,6 @@ import org.junit.Test; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.support.PropertiesLoaderUtils; -import org.springframework.integration.store.metadata.PropertiesPersistingMetadataStore; /** * @author Oleg Zhurakousky diff --git a/spring-integration-feed/src/main/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParser.java b/spring-integration-feed/src/main/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParser.java index c71a959827..f7f355cd18 100644 --- a/spring-integration-feed/src/main/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParser.java +++ b/spring-integration-feed/src/main/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParser.java @@ -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. @@ -23,6 +23,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.feed.inbound.FeedEntryMessageSource; import org.springframework.util.StringUtils; /** @@ -31,20 +32,23 @@ import org.springframework.util.StringUtils; * @author Josh Long * @author Oleg Zhurakousky * @author Mark Fisher + * @author Gunnar Hillert + * @author Artem Bilan * @since 2.0 */ public class FeedInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser { @Override protected BeanMetadataElement parseSource(final Element element, final ParserContext parserContext) { - BeanDefinitionBuilder sourceBuilder = BeanDefinitionBuilder.genericBeanDefinition( - "org.springframework.integration.feed.inbound.FeedEntryMessageSource"); + BeanDefinitionBuilder sourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(FeedEntryMessageSource.class); sourceBuilder.addConstructorArgValue(element.getAttribute("url")); + sourceBuilder.addConstructorArgValue(element.getAttribute(ID_ATTRIBUTE)); String feedFetcherRef = element.getAttribute("feed-fetcher"); if (StringUtils.hasText(feedFetcherRef)) { sourceBuilder.addConstructorArgReference(feedFetcherRef); } IntegrationNamespaceUtils.setReferenceIfAttributeDefined(sourceBuilder, element, "metadata-store"); + return sourceBuilder.getBeanDefinition(); } diff --git a/spring-integration-feed/src/main/java/org/springframework/integration/feed/inbound/FeedEntryMessageSource.java b/spring-integration-feed/src/main/java/org/springframework/integration/feed/inbound/FeedEntryMessageSource.java index 03fe2beff6..cdcd28c2c1 100644 --- a/spring-integration-feed/src/main/java/org/springframework/integration/feed/inbound/FeedEntryMessageSource.java +++ b/spring-integration-feed/src/main/java/org/springframework/integration/feed/inbound/FeedEntryMessageSource.java @@ -30,8 +30,8 @@ import org.springframework.messaging.MessagingException; import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.core.MessageSource; -import org.springframework.integration.store.metadata.MetadataStore; -import org.springframework.integration.store.metadata.SimpleMetadataStore; +import org.springframework.integration.metadata.MetadataStore; +import org.springframework.integration.metadata.SimpleMetadataStore; import org.springframework.integration.support.MessageBuilder; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -52,6 +52,7 @@ import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher; * @author Josh Long * @author Mario Gray * @author Oleg Zhurakousky + * @author Artem Bilan * @since 2.0 */ public class FeedEntryMessageSource extends IntegrationObjectSupport implements MessageSource { @@ -62,7 +63,7 @@ public class FeedEntryMessageSource extends IntegrationObjectSupport implements private final Queue entries = new ConcurrentLinkedQueue(); - private volatile String metadataKey; + private final String metadataKey; private volatile MetadataStore metadataStore; @@ -82,17 +83,19 @@ public class FeedEntryMessageSource extends IntegrationObjectSupport implements * If the feed URL has a protocol other than http*, consider providing a custom implementation of the * {@link FeedFetcher} via the alternate constructor. */ - public FeedEntryMessageSource(URL feedUrl) { - this(feedUrl, new HttpURLFeedFetcher(HashMapFeedInfoCache.getInstance())); + public FeedEntryMessageSource(URL feedUrl, String metadataKey) { + this(feedUrl, metadataKey, new HttpURLFeedFetcher(HashMapFeedInfoCache.getInstance())); } /** * Creates a FeedEntryMessageSource that will use the provided FeedFetcher to read from the given feed URL. */ - public FeedEntryMessageSource(URL feedUrl, FeedFetcher feedFetcher) { + public FeedEntryMessageSource(URL feedUrl, String metadataKey, FeedFetcher feedFetcher) { Assert.notNull(feedUrl, "feedUrl must not be null"); + Assert.notNull(metadataKey, "metadataKey must not be null"); Assert.notNull(feedFetcher, "feedFetcher must not be null"); this.feedUrl = feedUrl; + this.metadataKey = metadataKey + "." + this.feedUrl; this.feedFetcher = feedFetcher; } @@ -130,18 +133,7 @@ public class FeedEntryMessageSource extends IntegrationObjectSupport implements this.metadataStore = new SimpleMetadataStore(); } } - StringBuilder metadataKeyBuilder = new StringBuilder(); - if (StringUtils.hasText(this.getComponentType())) { - metadataKeyBuilder.append(this.getComponentType() + "."); - } - if (StringUtils.hasText(this.getComponentName())) { - metadataKeyBuilder.append(this.getComponentName() + "."); - } - else if (logger.isWarnEnabled()) { - logger.warn("FeedEntryMessageSource has no name. MetadataStore key might not be unique."); - } - metadataKeyBuilder.append(this.feedUrl); - this.metadataKey = metadataKeyBuilder.toString(); + String lastTimeValue = this.metadataStore.get(this.metadataKey); if (StringUtils.hasText(lastTimeValue)) { this.lastTime = Long.parseLong(lastTimeValue); @@ -237,6 +229,7 @@ public class FeedEntryMessageSource extends IntegrationObjectSupport implements } return (date2 == null) ? 1 : 0; } + } @@ -258,6 +251,7 @@ public class FeedEntryMessageSource extends IntegrationObjectSupport implements } } } + } } diff --git a/spring-integration-feed/src/main/resources/org/springframework/integration/feed/config/spring-integration-feed-3.0.xsd b/spring-integration-feed/src/main/resources/org/springframework/integration/feed/config/spring-integration-feed-3.0.xsd index c3182eaf84..954fe1c681 100644 --- a/spring-integration-feed/src/main/resources/org/springframework/integration/feed/config/spring-integration-feed-3.0.xsd +++ b/spring-integration-feed/src/main/resources/org/springframework/integration/feed/config/spring-integration-feed-3.0.xsd @@ -22,7 +22,28 @@ - + + + + The bean id of this Polling Endpoint; the MessageSource is also registered with this id + plus a suffix '.source'; also used as the + MetaDataStore key with suffix '.' + feedUrl - The URL for an RSS or ATOM feed. + + + + + + + + + + + + Identifies the channel attached to this adapter, to which messages will be sent. + + + + @@ -54,7 +75,7 @@ - + diff --git a/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParserTests-file-usage-context.xml b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParserTests-file-usage-context.xml index 216a0ee5a9..05b8997914 100644 --- a/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParserTests-file-usage-context.xml +++ b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParserTests-file-usage-context.xml @@ -21,6 +21,6 @@ - + diff --git a/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParserTests-file-usage-noid-context.xml b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParserTests-file-usage-noid-context.xml deleted file mode 100644 index 544aa96b6f..0000000000 --- a/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParserTests-file-usage-noid-context.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParserTests.java b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParserTests.java index 992a6396e6..4492201bf4 100644 --- a/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParserTests.java +++ b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedInboundChannelAdapterParserTests.java @@ -33,6 +33,7 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; + import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.messaging.Message; @@ -43,7 +44,7 @@ import org.springframework.messaging.MessageHandler; import org.springframework.integration.endpoint.SourcePollingChannelAdapter; import org.springframework.integration.feed.inbound.FeedEntryMessageSource; import org.springframework.integration.history.MessageHistory; -import org.springframework.integration.store.metadata.MetadataStore; +import org.springframework.integration.metadata.MetadataStore; import org.springframework.integration.test.util.TestUtils; import com.sun.syndication.feed.synd.SyndEntry; @@ -83,6 +84,7 @@ public class FeedInboundChannelAdapterParserTests { context.destroy(); } + public void validateSuccessfulHttpConfigurationWithCustomMetadataStore() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "FeedInboundChannelAdapterParserTests-http-context.xml", this.getClass()); @@ -98,7 +100,7 @@ public class FeedInboundChannelAdapterParserTests { @Test public void validateSuccessfulNewsRetrievalWithFileUrlAndMessageHistory() throws Exception { - File persisterFile = new File(System.getProperty("java.io.tmpdir") + "/spring-integration/", "message-store.properties"); + File persisterFile = new File(System.getProperty("java.io.tmpdir") + "/spring-integration/", "metadata-store.properties"); if (persisterFile.exists()) { persisterFile.delete(); } @@ -120,26 +122,6 @@ public class FeedInboundChannelAdapterParserTests { context.destroy(); } - @Test - public void validateSuccessfulNewsRetrievalWithFileUrlNoPersistentIdentifier() throws Exception{ - //Test file samples.rss has 3 news items - latch = spy(new CountDownLatch(3)); - ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( - "FeedInboundChannelAdapterParserTests-file-usage-noid-context.xml", this.getClass()); - latch.await(5, TimeUnit.SECONDS); - verify(latch, times(3)).countDown(); - context.destroy(); - - // since we are not deleting the persister file - // in this iteration no new feeds will be received and the latch will timeout - latch = spy(new CountDownLatch(3)); - context = new ClassPathXmlApplicationContext( - "FeedInboundChannelAdapterParserTests-file-usage-noid-context.xml", this.getClass()); - latch.await(5, TimeUnit.SECONDS); - verify(latch, times(3)).countDown(); - context.destroy(); - } - @Test @Ignore // goes against the real feed public void validateSuccessfulNewsRetrievalWithHttpUrl() throws Exception{ @@ -204,6 +186,11 @@ public class FeedInboundChannelAdapterParserTests { public String get(String key) { return null; } + + @Override + public String remove(String key) { + return null; + } } } diff --git a/spring-integration-feed/src/test/java/org/springframework/integration/feed/inbound/FeedEntryMessageSourceTests.java b/spring-integration-feed/src/test/java/org/springframework/integration/feed/inbound/FeedEntryMessageSourceTests.java index afd32231c6..4fe570d766 100644 --- a/spring-integration-feed/src/test/java/org/springframework/integration/feed/inbound/FeedEntryMessageSourceTests.java +++ b/spring-integration-feed/src/test/java/org/springframework/integration/feed/inbound/FeedEntryMessageSourceTests.java @@ -25,8 +25,9 @@ import java.net.URL; import org.junit.Before; import org.junit.Test; + +import org.springframework.integration.metadata.PropertiesPersistingMetadataStore; import org.springframework.messaging.Message; -import org.springframework.integration.store.metadata.PropertiesPersistingMetadataStore; import com.sun.syndication.feed.synd.SyndEntry; import com.sun.syndication.fetcher.FeedFetcher; @@ -52,14 +53,14 @@ public class FeedEntryMessageSourceTests { @Test(expected=IllegalArgumentException.class) public void testFailureWhenNotInitialized() throws Exception { URL url = new URL("file:src/test/java/org/springframework/integration/feed/sample.rss"); - FeedEntryMessageSource feedEntrySource = new FeedEntryMessageSource(url); + FeedEntryMessageSource feedEntrySource = new FeedEntryMessageSource(url, "foo"); feedEntrySource.receive(); } @Test public void testReceiveFeedWithNoEntries() throws Exception { URL url = new URL("file:src/test/java/org/springframework/integration/feed/empty.rss"); - FeedEntryMessageSource feedEntrySource = new FeedEntryMessageSource(url, this.feedFetcher); + FeedEntryMessageSource feedEntrySource = new FeedEntryMessageSource(url, "foo", this.feedFetcher); feedEntrySource.setBeanName("feedReader"); feedEntrySource.afterPropertiesSet(); assertNull(feedEntrySource.receive()); @@ -68,7 +69,7 @@ public class FeedEntryMessageSourceTests { @Test public void testReceiveFeedWithEntriesSorted() throws Exception { URL url = new URL("file:src/test/java/org/springframework/integration/feed/sample.rss"); - FeedEntryMessageSource source = new FeedEntryMessageSource(url, this.feedFetcher); + FeedEntryMessageSource source = new FeedEntryMessageSource(url, "foo", this.feedFetcher); source.setComponentName("feedReader"); source.afterPropertiesSet(); Message message1 = source.receive(); @@ -87,7 +88,7 @@ public class FeedEntryMessageSourceTests { @Test public void testReceiveFeedWithRealEntriesAndRepeatWithPersistentMetadataStore() throws Exception { URL url = new URL("file:src/test/java/org/springframework/integration/feed/sample.rss"); - FeedEntryMessageSource feedEntrySource = new FeedEntryMessageSource(url, this.feedFetcher); + FeedEntryMessageSource feedEntrySource = new FeedEntryMessageSource(url, "foo", this.feedFetcher); feedEntrySource.setBeanName("feedReader"); PropertiesPersistingMetadataStore metadataStore = new PropertiesPersistingMetadataStore(); metadataStore.afterPropertiesSet(); @@ -111,7 +112,7 @@ public class FeedEntryMessageSourceTests { metadataStore.afterPropertiesSet(); // now test that what's been read is no longer retrieved - feedEntrySource = new FeedEntryMessageSource(url, this.feedFetcher); + feedEntrySource = new FeedEntryMessageSource(url, "foo", this.feedFetcher); feedEntrySource.setBeanName("feedReader"); metadataStore = new PropertiesPersistingMetadataStore(); metadataStore.afterPropertiesSet(); @@ -127,7 +128,7 @@ public class FeedEntryMessageSourceTests { @Test public void testReceiveFeedWithRealEntriesAndRepeatNoPersistentMetadataStore() throws Exception { URL url = new URL("file:src/test/java/org/springframework/integration/feed/sample.rss"); - FeedEntryMessageSource feedEntrySource = new FeedEntryMessageSource(url, this.feedFetcher); + FeedEntryMessageSource feedEntrySource = new FeedEntryMessageSource(url, "foo", this.feedFetcher); feedEntrySource.setBeanName("feedReader"); feedEntrySource.afterPropertiesSet(); SyndEntry entry1 = feedEntrySource.receive().getPayload(); @@ -146,7 +147,7 @@ public class FeedEntryMessageSourceTests { // UNLIKE the previous test // now test that what's been read is read AGAIN - feedEntrySource = new FeedEntryMessageSource(url, this.feedFetcher); + feedEntrySource = new FeedEntryMessageSource(url, "foo", this.feedFetcher); feedEntrySource.setBeanName("feedReader"); feedEntrySource.afterPropertiesSet(); entry1 = feedEntrySource.receive().getPayload(); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileInboundChannelAdapterParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileInboundChannelAdapterParser.java index d6261d48de..404a58dfa8 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileInboundChannelAdapterParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileInboundChannelAdapterParser.java @@ -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,10 @@ package org.springframework.integration.file.config; +import org.w3c.dom.Element; + import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.xml.ParserContext; @@ -27,7 +28,6 @@ import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.file.locking.NioFileLocker; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; -import org.w3c.dom.Element; /** * Parser for the <inbound-channel-adapter> element of the 'file' namespace. @@ -53,9 +53,8 @@ public class FileInboundChannelAdapterParser extends AbstractPollingInboundChann builder.addPropertyReference("locker", lockerBeanName); } builder.addPropertyReference("filter", filterBeanName); - String beanName = BeanDefinitionReaderUtils.registerWithGeneratedName( - builder.getBeanDefinition(), parserContext.getRegistry()); - return new RuntimeBeanReference(beanName); + + return builder.getBeanDefinition(); } private String registerLocker(Element element, ParserContext parserContext) { diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests.java index cb3e7ffe22..856d4dd7a8 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests.java @@ -60,9 +60,8 @@ public class FtpParserInboundTests { fail("BeansException expected."); } catch (BeansException e) { + assertThat(e, Matchers.instanceOf(BeanCreationException.class)); Throwable cause = e.getCause(); - assertThat(cause, Matchers.instanceOf(BeanCreationException.class)); - cause = cause.getCause(); assertThat(cause, Matchers.instanceOf(MessagingException.class)); cause = cause.getCause(); assertThat(cause, Matchers.instanceOf(FileNotFoundException.class)); diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageHandler.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageHandler.java index b92bdbb672..ab91bdefb5 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageHandler.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageHandler.java @@ -38,7 +38,7 @@ import org.springframework.util.LinkedCaseInsensitiveMap; * {@link SqlParameterSourceFactory} abstraction, the default implementation of which wraps the message so that its bean * properties can be referred to by name in the query string E.g. * - *

+ * 
  * INSERT INTO FOOS (MESSAGE_ID, PAYLOAD) VALUES (:headers[id], :payload)
  * 
* @@ -101,6 +101,7 @@ public class JdbcMessageHandler extends AbstractMessageHandler { /** * Executes the update, passing the message into the {@link SqlParameterSourceFactory}. */ + @Override protected void handleMessageInternal(Message message) throws MessageRejectedException, MessageHandlingException, MessageDeliveryException { List> keys = executeUpdateQuery(message, keysGenerated); diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java index 40c35b0cc9..eb0faa6d9b 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java @@ -76,12 +76,6 @@ import org.springframework.util.StringUtils; * This message store shall be used for message channels only. *

*

- * NOTICE: This implementation may change for Spring Integration - * 3.0. It is provided for use-cases where the current {@link JdbcMessageStore} - * is not delivering the desired performance characteristics. - *

- * - *

* As such, the {@link JdbcChannelMessageStore} uses database specific SQL queries. *

*

@@ -89,8 +83,8 @@ import org.springframework.util.StringUtils; * database table only. The SQL scripts to create the necessary table are packaged * under org/springframework/integration/jdbc/messagestore/channel/schema-*.sql, * where * denotes the target database type. - *

+ *

+ * * @author Gunnar Hillert * @author Artem Bilan * @since 2.2 @@ -331,7 +325,7 @@ public class JdbcChannelMessageStore extends AbstractMessageGroupStore implement *

For this to work, you must setup the corresponding * {@link TransactionSynchronizationFactory}:

* - *
+	 * 
 	 * {@code
 	 * 
 	 *     
@@ -343,7 +337,7 @@ public class JdbcChannelMessageStore extends AbstractMessageGroupStore implement
 	 * This {@link TransactionSynchronizationFactory} is then referenced in the
 	 * transaction configuration of the poller:
 	 *
-	 * 
+	 * 
 	 * {@code
 	 * 
diff --git a/spring-integration-jms/src/main/java/org/springframework/integration/jms/config/JmsInboundChannelAdapterParser.java b/spring-integration-jms/src/main/java/org/springframework/integration/jms/config/JmsInboundChannelAdapterParser.java
index 9c7157b896..a9decccdd7 100644
--- a/spring-integration-jms/src/main/java/org/springframework/integration/jms/config/JmsInboundChannelAdapterParser.java
+++ b/spring-integration-jms/src/main/java/org/springframework/integration/jms/config/JmsInboundChannelAdapterParser.java
@@ -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.
@@ -19,18 +19,15 @@ package org.springframework.integration.jms.config;
 import org.w3c.dom.Element;
 
 import org.springframework.beans.BeanMetadataElement;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.parsing.BeanComponentDefinition;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
 import org.springframework.beans.factory.xml.ParserContext;
 import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
 import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
 import org.springframework.util.StringUtils;
 
 /**
- * Parser for the <inbound-channel-adapter/> element of the 'jms' namespace. 
- * 
+ * Parser for the <inbound-channel-adapter/> element of the 'jms' namespace.
+ *
  * @author Mark Fisher
  */
 public class JmsInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser {
@@ -87,9 +84,7 @@ public class JmsInboundChannelAdapterParser extends AbstractPollingInboundChanne
 			builder.addPropertyReference(JmsAdapterParserUtils.HEADER_MAPPER_PROPERTY, headerMapper);
 		}
 		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "selector", "messageSelector");
-		BeanDefinition beanDefinition = builder.getBeanDefinition();
-		String beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, parserContext.getRegistry());
-		return new BeanComponentDefinition(beanDefinition, beanName);
+		return builder.getBeanDefinition();
 	}
 
 }
diff --git a/spring-integration-jms/src/main/java/org/springframework/integration/jms/config/JmsMessageDrivenEndpointParser.java b/spring-integration-jms/src/main/java/org/springframework/integration/jms/config/JmsMessageDrivenEndpointParser.java
index 0e9a66f7de..3276096978 100644
--- a/spring-integration-jms/src/main/java/org/springframework/integration/jms/config/JmsMessageDrivenEndpointParser.java
+++ b/spring-integration-jms/src/main/java/org/springframework/integration/jms/config/JmsMessageDrivenEndpointParser.java
@@ -95,7 +95,8 @@ public class JmsMessageDrivenEndpointParser extends AbstractSingleBeanDefinition
 		String listenerBeanName = this.parseMessageListener(element, parserContext);
 		builder.addConstructorArgReference(containerBeanName);
 		builder.addConstructorArgReference(listenerBeanName);
-		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
+		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.AUTO_STARTUP);
+		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.PHASE);
 	}
 
 	private String parseMessageListenerContainer(Element element, ParserContext parserContext) {
diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml
index d19f5e6bf4..0ab4f1ab17 100644
--- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml
+++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml
@@ -13,18 +13,18 @@
 			http://www.springframework.org/schema/integration/jmx/spring-integration-jmx.xsd">
 
 	
-	
+
 	
 
 	
-	
-	
+
+	
 		
 		
 	
-	
+
 	
-	
+
 	
 
 
diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests.java
index 25faf51a4d..535f8b12ec 100644
--- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests.java
+++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests.java
@@ -1,11 +1,11 @@
 /*
  * 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.
@@ -36,7 +36,7 @@ public class PollingAdapterMBeanTests {
 
 	@Autowired
 	private MBeanServer server;
-	
+
 	@Test
 	public void testMessageSourceMBeanExists() throws Exception {
 		// System.err.println(server.queryNames(new ObjectName("*:type=MessageSource,*"), null));
diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParser.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParser.java
index 216f642ec6..a236ce1b26 100644
--- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParser.java
+++ b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParser.java
@@ -15,13 +15,11 @@
  */
 package org.springframework.integration.mongodb.config;
 
-import org.springframework.beans.factory.config.BeanDefinition;
 import org.w3c.dom.Element;
 
 import org.springframework.beans.BeanMetadataElement;
-import org.springframework.beans.factory.config.RuntimeBeanReference;
+import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
 import org.springframework.beans.factory.xml.ParserContext;
 import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
 import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
@@ -52,8 +50,7 @@ public class MongoDbInboundChannelAdapterParser extends AbstractPollingInboundCh
 		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "entity-class");
 		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "expect-single-result");
 
-		String beanName = BeanDefinitionReaderUtils.registerWithGeneratedName(
-				builder.getBeanDefinition(), parserContext.getRegistry());
-		return new RuntimeBeanReference(beanName);
+		return builder.getBeanDefinition();
 	}
+
 }
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParser.java
index 6ade9c2c30..13aab72337 100644
--- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParser.java
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParser.java
@@ -23,20 +23,21 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.xml.ParserContext;
 import org.springframework.integration.config.xml.AbstractChannelAdapterParser;
 import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
+import org.springframework.integration.redis.inbound.RedisInboundChannelAdapter;
 import org.springframework.util.StringUtils;
 
 /**
  * @author Oleg Zhurakousky
  * @author Mark Fisher
  * @author Gary Russell
+ * @author Artem Bilan
  * @since 2.1
  */
 public class RedisInboundChannelAdapterParser extends AbstractChannelAdapterParser {
 
 	@Override
 	protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) {
-		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
-				"org.springframework.integration.redis.inbound.RedisInboundChannelAdapter");
+		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisInboundChannelAdapter.class);
 		String connectionFactory = element.getAttribute("connection-factory");
 		if (!StringUtils.hasText(connectionFactory)) {
 			connectionFactory = "redisConnectionFactory";
@@ -46,7 +47,8 @@ public class RedisInboundChannelAdapterParser extends AbstractChannelAdapterPars
 		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "topics");
 		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel");
 		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converter");
-		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "serializer");
+		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "serializer", true);
+
 		return builder.getBeanDefinition();
 	}
 
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisNamespaceHandler.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisNamespaceHandler.java
index 834863fee4..c13c32bc06 100644
--- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisNamespaceHandler.java
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisNamespaceHandler.java
@@ -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.
@@ -21,6 +21,7 @@ import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHa
  *  Namespace handler for Spring Integration's 'redis' namespace.
  *
  * @author Oleg Zhurakousky
+ * @author Artem Bilan
  * @since 2.1
  */
 public class RedisNamespaceHandler extends AbstractIntegrationNamespaceHandler {
@@ -31,5 +32,7 @@ public class RedisNamespaceHandler extends AbstractIntegrationNamespaceHandler {
 		registerBeanDefinitionParser("store-inbound-channel-adapter", new RedisStoreInboundChannelAdapterParser());
 		registerBeanDefinitionParser("store-outbound-channel-adapter", new RedisStoreOutboundChannelAdapterParser());
 		registerBeanDefinitionParser("outbound-channel-adapter", new RedisOutboundChannelAdapterParser());
+		registerBeanDefinitionParser("queue-inbound-channel-adapter", new RedisQueueInboundChannelAdapterParser());
+		registerBeanDefinitionParser("queue-outbound-channel-adapter", new RedisQueueOutboundChannelAdapterParser());
 	}
 }
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParser.java
index b97e31572d..eb507557f1 100644
--- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParser.java
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParser.java
@@ -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.
@@ -18,32 +18,41 @@ package org.springframework.integration.redis.config;
 
 import org.w3c.dom.Element;
 
+import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.support.AbstractBeanDefinition;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.xml.ParserContext;
 import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
 import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
+import org.springframework.integration.redis.outbound.RedisPublishingMessageHandler;
 import org.springframework.util.StringUtils;
 
 /**
+ * Parser for the {@code } component.
+ *
  * @author Oleg Zhurakousky
  * @author Mark Fisher
+ * @author Artem Bilan
  * @since 2.1
  */
 public class RedisOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
 
 	@Override
 	protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
-		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
-				"org.springframework.integration.redis.outbound.RedisPublishingMessageHandler");
+		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisPublishingMessageHandler.class);
 		String connectionFactory = element.getAttribute("connection-factory");
 		if (!StringUtils.hasText(connectionFactory)) {
 			connectionFactory = "redisConnectionFactory";
 		}
 		builder.addConstructorArgReference(connectionFactory);
-		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "topic", "defaultTopic");
+
 		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converter");
 		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "serializer");
+
+		BeanDefinition topicExpression = IntegrationNamespaceUtils
+				.createExpressionDefinitionFromValueOrExpression("topic", "topic-expression", parserContext, element, true);
+		builder.addPropertyValue("topicExpression", topicExpression);
+
 		return builder.getBeanDefinition();
 	}
 
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueInboundChannelAdapterParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueInboundChannelAdapterParser.java
new file mode 100644
index 0000000000..18996384da
--- /dev/null
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueInboundChannelAdapterParser.java
@@ -0,0 +1,58 @@
+/*
+ * 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.redis.config;
+
+import org.w3c.dom.Element;
+
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.integration.config.xml.AbstractChannelAdapterParser;
+import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
+import org.springframework.integration.redis.inbound.RedisQueueMessageDrivenEndpoint;
+import org.springframework.util.StringUtils;
+
+/**
+ * Parser for the <queue-inbound-channel-adapter> element of the 'redis' namespace.
+ *
+ * @author Artem Bilan
+ * @since 3.0
+ */
+public class RedisQueueInboundChannelAdapterParser extends AbstractChannelAdapterParser {
+
+	@Override
+	protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) {
+		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisQueueMessageDrivenEndpoint.class);
+		builder.addConstructorArgValue(element.getAttribute("queue"));
+
+		String connectionFactory = element.getAttribute("connection-factory");
+		if (!StringUtils.hasText(connectionFactory)) {
+			connectionFactory = "redisConnectionFactory";
+		}
+		builder.addConstructorArgReference(connectionFactory);
+
+		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "serializer", true);
+		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "task-executor");
+		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel");
+		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "expect-message");
+		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "receive-timeout");
+		builder.addPropertyReference("outputChannel", channelName);
+
+		return builder.getBeanDefinition();
+	}
+
+}
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParser.java
new file mode 100644
index 0000000000..9e0653e814
--- /dev/null
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParser.java
@@ -0,0 +1,57 @@
+/*
+ * 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.redis.config;
+
+import org.w3c.dom.Element;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
+import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
+import org.springframework.integration.redis.outbound.RedisQueueOutboundChannelAdapter;
+import org.springframework.util.StringUtils;
+
+/**
+ * Parser for the <int-redis:queue-outbound-channel-adapter> element.
+ *
+ * @author Artem Bilan
+ * @since 3.0
+ */
+public class RedisQueueOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
+
+	@Override
+	protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
+		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisQueueOutboundChannelAdapter.class);
+		BeanDefinition queueExpression = IntegrationNamespaceUtils
+				.createExpressionDefinitionFromValueOrExpression("queue", "queue-expression", parserContext, element, true);
+		builder.addConstructorArgValue(queueExpression);
+
+		String connectionFactory = element.getAttribute("connection-factory");
+		if (!StringUtils.hasText(connectionFactory)) {
+			connectionFactory = "redisConnectionFactory";
+		}
+		builder.addConstructorArgReference(connectionFactory);
+
+		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload");
+		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "serializer");
+
+		return builder.getBeanDefinition();
+	}
+
+}
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParser.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParser.java
index 1920f4ad97..0e73cd3cd7 100644
--- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParser.java
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/config/RedisStoreInboundChannelAdapterParser.java
@@ -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.
@@ -20,9 +20,7 @@ import org.w3c.dom.Element;
 
 import org.springframework.beans.BeanMetadataElement;
 import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.config.RuntimeBeanReference;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
 import org.springframework.beans.factory.xml.ParserContext;
 import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
 import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
@@ -62,9 +60,8 @@ public class RedisStoreInboundChannelAdapterParser extends AbstractPollingInboun
 						parserContext, element, atLeastOneRequired);
 		builder.addConstructorArgValue(expressionDef);
 		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "collection-type");
-		String beanName = BeanDefinitionReaderUtils.registerWithGeneratedName(
-				builder.getBeanDefinition(), parserContext.getRegistry());
-		return new RuntimeBeanReference(beanName);
+
+		return builder.getBeanDefinition();
 	}
 
 }
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/RedisExceptionEvent.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/RedisExceptionEvent.java
new file mode 100644
index 0000000000..62115bfa6a
--- /dev/null
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/RedisExceptionEvent.java
@@ -0,0 +1,30 @@
+/*
+ * 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.redis.event;
+
+/**
+ * @author Artem Bilan
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class RedisExceptionEvent extends RedisIntegrationEvent {
+
+	public RedisExceptionEvent(Object source, Throwable cause) {
+		super(source, cause);
+	}
+
+}
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/RedisIntegrationEvent.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/RedisIntegrationEvent.java
new file mode 100644
index 0000000000..e2a91068d1
--- /dev/null
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/RedisIntegrationEvent.java
@@ -0,0 +1,37 @@
+/*
+ * 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.redis.event;
+
+import org.springframework.integration.event.IntegrationEvent;
+
+/**
+ * @author Artem Bilan
+ * @since 3.0
+ *
+ */
+@SuppressWarnings("serial")
+public abstract class RedisIntegrationEvent extends IntegrationEvent {
+
+	public RedisIntegrationEvent(Object source) {
+		super(source);
+	}
+
+	public RedisIntegrationEvent(Object source, Throwable cause) {
+		super(source, cause);
+	}
+
+}
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/package-info.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/package-info.java
new file mode 100644
index 0000000000..3c4d6dec86
--- /dev/null
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/event/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Events generated by the redis module
+ */
+package org.springframework.integration.redis.event;
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapter.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapter.java
index 920ee7fd9b..737a8c6ca4 100644
--- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapter.java
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007-2012 the original author or authors
+ * Copyright 2007-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.
@@ -34,9 +34,9 @@ import org.springframework.util.Assert;
 /**
  * @author Mark Fisher
  * @author Oleg Zhurakousky
+ * @author Gary Russell
  * @since 2.1
  */
-@SuppressWarnings("rawtypes")
 public class RedisInboundChannelAdapter extends MessageProducerSupport {
 
 	private final RedisMessageListenerContainer container = new RedisMessageListenerContainer();
@@ -53,7 +53,6 @@ public class RedisInboundChannelAdapter extends MessageProducerSupport {
 	}
 
 	public void setSerializer(RedisSerializer serializer) {
-		Assert.notNull(serializer, "'serializer' must not be null");
 		this.serializer = serializer;
 	}
 
@@ -100,17 +99,16 @@ public class RedisInboundChannelAdapter extends MessageProducerSupport {
 		this.container.stop();
 	}
 
-	@SuppressWarnings("unchecked")
-	private Message convertMessage(String s) {
-		return this.messageConverter.toMessage(s, null);
+	private Message convertMessage(Object object) {
+		return this.messageConverter.toMessage(object, null);
 	}
 
 
 	private class MessageListenerDelegate {
 
 		@SuppressWarnings("unused")
-		public void handleMessage(String s) {
-			sendMessage(convertMessage(s));
+		public void handleMessage(Object object) {
+			sendMessage(convertMessage(object));
 		}
 	}
 
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpoint.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpoint.java
index c63bab5f38..e01afbc08f 100644
--- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpoint.java
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpoint.java
@@ -15,10 +15,12 @@
  */
 package org.springframework.integration.redis.inbound;
 
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
 import org.springframework.core.task.SimpleAsyncTaskExecutor;
-import org.springframework.core.task.TaskExecutor;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.BoundListOperations;
 import org.springframework.data.redis.core.RedisTemplate;
@@ -27,7 +29,9 @@ import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 import org.springframework.integration.channel.MessagePublishingErrorHandler;
 import org.springframework.integration.endpoint.MessageProducerSupport;
+import org.springframework.integration.redis.event.RedisExceptionEvent;
 import org.springframework.integration.support.MessageBuilder;
+import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
 import org.springframework.integration.util.ErrorHandlingTaskExecutor;
 import org.springframework.jmx.export.annotation.ManagedMetric;
 import org.springframework.jmx.export.annotation.ManagedOperation;
@@ -44,15 +48,19 @@ import org.springframework.util.Assert;
  * @since 3.0
  */
 @ManagedResource
-public class RedisQueueMessageDrivenEndpoint extends MessageProducerSupport {
+public class RedisQueueMessageDrivenEndpoint extends MessageProducerSupport implements ApplicationEventPublisherAware {
 
 	public static final long DEFAULT_RECEIVE_TIMEOUT = 1000;
 
+	public static final long DEFAULT_RECOVERY_INTERVAL = 5000;
+
 	private final BoundListOperations boundListOperations;
 
-	private MessageChannel errorChannel;
+	private volatile ApplicationEventPublisher applicationEventPublisher;
 
-	private volatile TaskExecutor taskExecutor;
+	private volatile MessageChannel errorChannel;
+
+	private volatile Executor taskExecutor;
 
 	private volatile RedisSerializer serializer = new JdkSerializationRedisSerializer();
 
@@ -60,6 +68,8 @@ public class RedisQueueMessageDrivenEndpoint extends MessageProducerSupport {
 
 	private volatile long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT;
 
+	private volatile long recoveryInterval = DEFAULT_RECOVERY_INTERVAL;
+
 	private volatile boolean active;
 
 	private volatile boolean listening;
@@ -79,6 +89,11 @@ public class RedisQueueMessageDrivenEndpoint extends MessageProducerSupport {
 		this.boundListOperations = template.boundListOps(queueName);
 	}
 
+	@Override
+	public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
+		this.applicationEventPublisher = applicationEventPublisher;
+	}
+
 	public void setSerializer(RedisSerializer serializer) {
 		this.serializer = serializer;
 	}
@@ -117,7 +132,7 @@ public class RedisQueueMessageDrivenEndpoint extends MessageProducerSupport {
 		this.receiveTimeout = receiveTimeout;
 	}
 
-	public void setTaskExecutor(TaskExecutor taskExecutor) {
+	public void setTaskExecutor(Executor taskExecutor) {
 		this.taskExecutor = taskExecutor;
 	}
 
@@ -127,6 +142,10 @@ public class RedisQueueMessageDrivenEndpoint extends MessageProducerSupport {
 		this.errorChannel = errorChannel;
 	}
 
+	public void setRecoveryInterval(long recoveryInterval) {
+		this.recoveryInterval = recoveryInterval;
+	}
+
 	@Override
 	protected void onInit() {
 		super.onInit();
@@ -138,7 +157,8 @@ public class RedisQueueMessageDrivenEndpoint extends MessageProducerSupport {
 			this.taskExecutor = new SimpleAsyncTaskExecutor((beanName == null ? "" : beanName + "-") + this.getComponentType());
 		}
 		if (!(this.taskExecutor instanceof ErrorHandlingTaskExecutor)) {
-			MessagePublishingErrorHandler errorHandler = new MessagePublishingErrorHandler();
+			MessagePublishingErrorHandler errorHandler =
+					new MessagePublishingErrorHandler(new BeanFactoryChannelResolver(this.getBeanFactory()));
 			errorHandler.setDefaultErrorChannel(this.errorChannel);
 			this.taskExecutor = new ErrorHandlingTaskExecutor(this.taskExecutor, errorHandler);
 		}
@@ -146,14 +166,24 @@ public class RedisQueueMessageDrivenEndpoint extends MessageProducerSupport {
 
 	@Override
 	public String getComponentType() {
-		return "int-redis:message-driven-channel-adapter";
+		return "redis:queue-inbound-channel-adapter";
 	}
 
 	@SuppressWarnings("unchecked")
 	private void popMessageAndSend() {
 		Message message = null;
 
-		byte[] value = this.boundListOperations.rightPop(this.receiveTimeout, TimeUnit.MILLISECONDS);
+		byte[] value = null;
+		try {
+			value = this.boundListOperations.rightPop(this.receiveTimeout, TimeUnit.MILLISECONDS);
+		}
+		catch (Exception e) {
+			logger.error("Failed to execute listening task. Will attempt to resubmit in " + this.recoveryInterval + " milliseconds.", e);
+			this.listening = false;
+			this.sleepBeforeRecoveryAttempt();
+			this.publishException(e);
+			return;
+		}
 
 		if (value != null) {
 			if (this.expectMessage) {
@@ -186,6 +216,32 @@ public class RedisQueueMessageDrivenEndpoint extends MessageProducerSupport {
 		}
 	}
 
+	/**
+	 * Sleep according to the specified recovery interval.
+	 * Called between recovery attempts.
+	 */
+	private void sleepBeforeRecoveryAttempt() {
+		if (this.recoveryInterval > 0) {
+			try {
+				Thread.sleep(this.recoveryInterval);
+			}
+			catch (InterruptedException e) {
+				logger.debug("Thread interrupted while sleeping the recovery interval");
+			}
+		}
+	}
+
+	private void publishException(Exception e) {
+		if (this.applicationEventPublisher != null) {
+			this.applicationEventPublisher.publishEvent(new RedisExceptionEvent(this, e));
+		}
+		else {
+			if (logger.isDebugEnabled()) {
+				logger.debug("No application event publisher for exception: " + e.getMessage());
+			}
+		}
+	}
+
 	private void restart() {
 		this.taskExecutor.execute(new ListenerTask());
 	}
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/metadata/RedisMetadataStore.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/metadata/RedisMetadataStore.java
similarity index 88%
rename from spring-integration-redis/src/main/java/org/springframework/integration/redis/store/metadata/RedisMetadataStore.java
rename to spring-integration-redis/src/main/java/org/springframework/integration/redis/metadata/RedisMetadataStore.java
index b837625b41..e8aa0609c7 100644
--- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/metadata/RedisMetadataStore.java
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/metadata/RedisMetadataStore.java
@@ -11,13 +11,13 @@
  * specific language governing permissions and limitations under the License.
  */
 
-package org.springframework.integration.redis.store.metadata;
+package org.springframework.integration.redis.metadata;
 
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.BoundValueOperations;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.StringRedisTemplate;
-import org.springframework.integration.store.metadata.MetadataStore;
+import org.springframework.integration.metadata.MetadataStore;
 import org.springframework.util.Assert;
 
 /**
@@ -65,4 +65,13 @@ public class RedisMetadataStore implements MetadataStore {
 		BoundValueOperations ops = this.redisTemplate.boundValueOps(key);
 		return ops.get();
 	}
+
+	@Override
+	public String remove(String key) {
+		Assert.notNull(key, "'key' must not be null.");
+		String value = this.get(key);
+		this.redisTemplate.delete(key);
+		return value;
+	}
+
 }
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/metadata/package-info.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/metadata/package-info.java
new file mode 100644
index 0000000000..65840e9612
--- /dev/null
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/metadata/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Provides support for Redis-based
+ * {@link org.springframework.integration.metadata.MetadataStore}s.
+ */
+package org.springframework.integration.redis.metadata;
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandler.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandler.java
index 2c5baa8bf1..556400d177 100644
--- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandler.java
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007-2011 the original author or authors
+ * Copyright 2007-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,9 +17,13 @@
 package org.springframework.integration.redis.outbound;
 
 import org.springframework.data.redis.connection.RedisConnectionFactory;
-import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.common.LiteralExpression;
+import org.springframework.integration.expression.IntegrationEvaluationContextAware;
 import org.springframework.integration.handler.AbstractMessageHandler;
 import org.springframework.integration.support.converter.SimpleMessageConverter;
 import org.springframework.messaging.Message;
@@ -28,22 +32,32 @@ import org.springframework.util.Assert;
 
 /**
  * @author Mark Fisher
+ * @author Artem Bilan
  * @since 2.1
  */
-@SuppressWarnings("rawtypes")
-public class RedisPublishingMessageHandler extends AbstractMessageHandler {
+public class RedisPublishingMessageHandler extends AbstractMessageHandler implements IntegrationEvaluationContextAware {
 
-	private final StringRedisTemplate template;
+	private final RedisTemplate template;
+
+	private volatile EvaluationContext evaluationContext;
 
 	private volatile MessageConverter messageConverter = new SimpleMessageConverter();
 
-	private volatile String defaultTopic;
-
 	private volatile RedisSerializer serializer = new StringRedisSerializer();
 
+	private volatile Expression topicExpression;
+
 	public RedisPublishingMessageHandler(RedisConnectionFactory connectionFactory) {
 		Assert.notNull(connectionFactory, "connectionFactory must not be null");
-		this.template = new StringRedisTemplate(connectionFactory);
+		this.template = new RedisTemplate();
+		this.template.setConnectionFactory(connectionFactory);
+		this.template.setEnableDefaultSerializer(false);
+		this.template.afterPropertiesSet();
+	}
+
+	@Override
+	public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) {
+		this.evaluationContext = evaluationContext;
 	}
 
 	public void setSerializer(RedisSerializer serializer) {
@@ -56,28 +70,42 @@ public class RedisPublishingMessageHandler extends AbstractMessageHandler {
 		this.messageConverter = messageConverter;
 	}
 
+	/**
+	 * @deprecated in favor of {@link #setTopicExpression(Expression)} or {@link #setTopic(String)}
+	 */
+	@Deprecated
 	public void setDefaultTopic(String defaultTopic) {
-		this.defaultTopic = defaultTopic;
+		Assert.hasText(defaultTopic, "'defaultTopic' must not be an empty string.");
+		this.setTopicExpression(new LiteralExpression(defaultTopic));
 	}
 
-	private String determineTopic(Message message) {
-		// TODO: add support for determining topic by evaluating SpEL against the Message
-		Assert.hasText(this.defaultTopic, "Failed to determine Redis topic " +
-				"from Message, and no defaultTopic has been provided.");
-		return this.defaultTopic;
+	public void setTopic(String topic) {
+		Assert.hasText(topic, "'topic' must not be an empty string.");
+		this.setTopicExpression(new LiteralExpression(topic));
 	}
 
-	@SuppressWarnings("unchecked")
-	@Override
-	protected void handleMessageInternal(Message message) throws Exception {
-		String topic = this.determineTopic(message);
-		Object value = this.messageConverter.fromMessage(message, Object.class);
-		this.template.convertAndSend(topic, value.toString());
+	public void setTopicExpression(Expression topicExpression) {
+		Assert.notNull(topicExpression, "'topicExpression' must not be null.");
+		this.topicExpression = topicExpression;
 	}
 
 	@Override
 	protected void onInit() throws Exception {
-		this.template.setValueSerializer(this.serializer);
-		this.template.afterPropertiesSet();
+		Assert.notNull(topicExpression, "'topicExpression' must not be null.");
 	}
+
+	@Override
+	@SuppressWarnings("unchecked")
+	protected void handleMessageInternal(Message message) throws Exception {
+		String topic = this.topicExpression.getValue(this.evaluationContext, message, String.class);
+		Object value = this.messageConverter.fromMessage(message, null);
+
+		if (value instanceof byte[]) {
+			this.template.convertAndSend(topic, value);
+		}
+		else {
+			this.template.convertAndSend(topic, ((RedisSerializer) this.serializer).serialize(value));
+		}
+	}
+
 }
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapter.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapter.java
index 3e95180850..b6626c5825 100644
--- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapter.java
+++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapter.java
@@ -43,7 +43,7 @@ public class RedisQueueOutboundChannelAdapter extends AbstractMessageHandler imp
 
 	private final Expression queueNameExpression;
 
-	private EvaluationContext evaluationContext;
+	private volatile EvaluationContext evaluationContext;
 
 	private volatile boolean extractPayload = true;
 
@@ -84,7 +84,7 @@ public class RedisQueueOutboundChannelAdapter extends AbstractMessageHandler imp
 
 	@Override
 	public String getComponentType() {
-		return "int-redis:outbound-channel-adapter";
+		return "redis:outbound-channel-adapter";
 	}
 
 	@Override
diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/metadata/package-info.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/metadata/package-info.java
deleted file mode 100644
index 8e0a51cc48..0000000000
--- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/store/metadata/package-info.java
+++ /dev/null
@@ -1,5 +0,0 @@
-/**
- * Provides support for Redis-based
- * {@link org.springframework.integration.store.metadata.MetadataStore}s.
- */
-package org.springframework.integration.redis.store.metadata;
diff --git a/spring-integration-redis/src/main/resources/org/springframework/integration/redis/config/spring-integration-redis-3.0.xsd b/spring-integration-redis/src/main/resources/org/springframework/integration/redis/config/spring-integration-redis-3.0.xsd
index f4df071b07..d34cfb6e0e 100644
--- a/spring-integration-redis/src/main/resources/org/springframework/integration/redis/config/spring-integration-redis-3.0.xsd
+++ b/spring-integration-redis/src/main/resources/org/springframework/integration/redis/config/spring-integration-redis-3.0.xsd
@@ -82,10 +82,7 @@
 			
 				
 				
 					
@@ -177,7 +174,9 @@
 				
 					
 						
-						Reference to an instance of org.springframework.data.redis.serializer.RedisSerializer
+						Reference to an instance of org.springframework.data.redis.serializer.RedisSerializer.
+						This attribute can be an empty string, which results in 'null' being used by the underlying adapter,
+						meaning no serializer is used and the raw byte[] will be the message payload.
 						
 						
 							
@@ -200,7 +199,22 @@
 					
 						
 					
-					
+					
+						
+							
+						
+					
+					
+						
+							
+						
+					
 					
 						
 							
 	
 
+	
+		
+			
+				Defines a Message Driven Endpoint for listening a Redis queue.
+			
+		
+		
+			
+				
+					
+						
+							
+								Redis queue name.
+							
+						
+					
+					
+						
+							
+						
+					
+					
+						
+							
+								
+									Reference to an instance of org.springframework.data.redis.serializer.RedisSerializer.
+									It can be specified as an empty String value, which means the Endpoint's 'serializer' property is
+									set to 'null', in which case the Message will contain the raw byte[] payload.
+								
+								
+									
+								
+							
+						
+					
+					
+						
+							
+								Specify the timeout in milliseconds to wait for the result of the
+								'rightPop' operation on Redis queue.
+								Default is 1 second.
+							
+						
+					
+					
+						
+							
+								When true, specifies that the 'byte[]' from a Redis message should be deserialized
+								as an entire Spring Integration Message. Otherwise the data becomes just the
+								payload of the message (deserialized or not).
+								If this attribute is 'true', the 'serializer' must not be an empty String.
+								Default is 'false'.
+							
+						
+					
+					
+						
+							
+							
+								
+									
+								
+							
+						
+					
+				
+			
+		
+	
+
+	
+		
+			
+				Defines an outbound Redis Queue Message-sending Channel Adapter.
+			
+		
+		
+			
+				
+					
+						
+					
+					
+						
+							
+						
+					
+					
+						
+							
+						
+					
+					
+						
+							
+						
+					
+					
+						
+							
+								
+									Reference to an instance of org.springframework.data.redis.serializer.RedisSerializer
+								
+								
+									
+								
+							
+						
+					
+					
+						
+							
+								Specifies if the Message payload or the entire (serialized) Message will be send to the Redis queue.
+								Default is 'true'.
+							
+						
+					
+				
+			
+		
+	
+
 	
 		
 			
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/channel/SubscribableRedisChannelTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/channel/SubscribableRedisChannelTests.java
index 079eee8ba0..a4bb755d22 100644
--- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/channel/SubscribableRedisChannelTests.java
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/channel/SubscribableRedisChannelTests.java
@@ -30,7 +30,6 @@ import java.util.concurrent.TimeUnit;
 import org.junit.Test;
 
 import org.springframework.beans.factory.BeanFactory;
-import org.springframework.data.redis.connection.RedisConnection;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.listener.RedisMessageListenerContainer;
 import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
@@ -53,7 +52,7 @@ public class SubscribableRedisChannelTests extends RedisAvailableTests {
 
 	@Test
 	@RedisAvailable
-	public void pubSubChannelTest() throws Exception{
+	public void pubSubChannelTest() throws Exception {
 		RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
 
 		SubscribableRedisChannel channel = new SubscribableRedisChannel(connectionFactory, "si.test.channel");
@@ -61,14 +60,7 @@ public class SubscribableRedisChannelTests extends RedisAvailableTests {
 		channel.afterPropertiesSet();
 		channel.start();
 
-		RedisConnection connection = TestUtils.getPropertyValue(channel, "container.subscriptionTask.connection",
-				RedisConnection.class);
-
-		int n = 0;
-		while (n++ < 100 && !connection.isSubscribed()) {
-			Thread.sleep(100);
-		}
-		assertTrue(n < 100);
+		this.awaitContainerSubscribed(TestUtils.getPropertyValue(channel, "container", RedisMessageListenerContainer.class));
 
 		final CountDownLatch latch = new CountDownLatch(3);
 		MessageHandler handler = new MessageHandler() {
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests-context.xml
index 832b822114..ac2fa84b26 100644
--- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests-context.xml
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests-context.xml
@@ -32,4 +32,7 @@
 
 	
 
+	
+
+
 
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests.java
index fcd59a3e6a..f34cef85a7 100644
--- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests.java
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests.java
@@ -17,6 +17,8 @@
 package org.springframework.integration.redis.config;
 
 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 org.junit.Test;
@@ -68,6 +70,10 @@ public class RedisInboundChannelAdapterParserTests extends RedisAvailableTests {
 		Object converterBean = context.getBean("testConverter");
 		assertEquals(converterBean, accessor.getPropertyValue("messageConverter"));
 		assertEquals(context.getBean("serializer"), accessor.getPropertyValue("serializer"));
+
+		Object bean = context.getBean("withoutSerializer.adapter");
+		assertNotNull(bean);
+		assertNull(TestUtils.getPropertyValue(bean, "serializer"));
 	}
 
 	@Test
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisMessageDrivenEndpointParserTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisMessageDrivenEndpointParserTests-context.xml
new file mode 100644
index 0000000000..6c7558adf3
--- /dev/null
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisMessageDrivenEndpointParserTests-context.xml
@@ -0,0 +1,44 @@
+
+
+
+	
+		
+	
+
+	
+
+	
+
+	
+
+	
+
+	
+		
+		
+	
+
+	
+
+	
+
+
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisMessageDrivenEndpointParserTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisMessageDrivenEndpointParserTests.java
new file mode 100644
index 0000000000..56ba70bba6
--- /dev/null
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisMessageDrivenEndpointParserTests.java
@@ -0,0 +1,114 @@
+/*
+ * 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.redis.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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 org.hamcrest.Matchers;
+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.core.task.TaskExecutor;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.integration.MessageChannel;
+import org.springframework.integration.redis.inbound.RedisQueueMessageDrivenEndpoint;
+import org.springframework.integration.test.util.TestUtils;
+import org.springframework.integration.util.ErrorHandlingTaskExecutor;
+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 RedisMessageDrivenEndpointParserTests {
+
+	@Autowired
+	@Qualifier("redisConnectionFactory")
+	private RedisConnectionFactory connectionFactory;
+
+	@Autowired
+	@Qualifier("customRedisConnectionFactory")
+	private RedisConnectionFactory customRedisConnectionFactory;
+
+	@Autowired
+	@Qualifier("defaultAdapter.adapter")
+	private RedisQueueMessageDrivenEndpoint defaultAdapter;
+
+	@Autowired
+	@Qualifier("defaultAdapter")
+	private MessageChannel defaultAdapterChannel;
+
+	@Autowired
+	@Qualifier("customAdapter")
+	private RedisQueueMessageDrivenEndpoint customAdapter;
+
+	@Autowired
+	@Qualifier("errorChannel")
+	private MessageChannel errorChannel;
+
+	@Autowired
+	@Qualifier("sendChannel")
+	private MessageChannel sendChannel;
+
+	@Autowired
+	@Qualifier("executor")
+	private TaskExecutor taskExecutor;
+
+
+	@Autowired
+	private RedisSerializer serializer;
+
+	@Test
+	public void testInt3017DefaultConfig() {
+		assertSame(this.connectionFactory, TestUtils.getPropertyValue(this.defaultAdapter, "boundListOperations.ops.template.connectionFactory"));
+		assertEquals("si.test.Int3017.Inbound1", TestUtils.getPropertyValue(this.defaultAdapter, "boundListOperations.key"));
+		assertFalse(TestUtils.getPropertyValue(this.defaultAdapter, "expectMessage", Boolean.class));
+		assertEquals(new Long(1000), TestUtils.getPropertyValue(this.defaultAdapter, "receiveTimeout", Long.class));
+		assertNull(TestUtils.getPropertyValue(this.defaultAdapter, "errorChannel"));
+		assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "taskExecutor"), Matchers.instanceOf(ErrorHandlingTaskExecutor.class));
+		assertThat(TestUtils.getPropertyValue(this.defaultAdapter, "serializer"), Matchers.instanceOf(JdkSerializationRedisSerializer.class));
+		assertTrue(TestUtils.getPropertyValue(this.defaultAdapter, "autoStartup", Boolean.class));
+		assertSame(this.defaultAdapterChannel, TestUtils.getPropertyValue(this.defaultAdapter, "outputChannel"));
+	}
+
+	@Test
+	public void testInt3017CustomConfig() {
+		assertSame(this.customRedisConnectionFactory, TestUtils.getPropertyValue(this.customAdapter, "boundListOperations.ops.template.connectionFactory"));
+		assertEquals("si.test.Int3017.Inbound2", TestUtils.getPropertyValue(this.customAdapter, "boundListOperations.key"));
+		assertTrue(TestUtils.getPropertyValue(this.customAdapter, "expectMessage", Boolean.class));
+		assertEquals(new Long(2000), TestUtils.getPropertyValue(this.customAdapter, "receiveTimeout", Long.class));
+		assertSame(this.errorChannel, TestUtils.getPropertyValue(this.customAdapter, "errorChannel"));
+		assertSame(this.taskExecutor, TestUtils.getPropertyValue(this.customAdapter, "taskExecutor"));
+		assertSame(this.serializer, TestUtils.getPropertyValue(this.customAdapter, "serializer"));
+		assertFalse(TestUtils.getPropertyValue(this.customAdapter, "autoStartup", Boolean.class));
+		assertEquals(new Integer(100), TestUtils.getPropertyValue(this.customAdapter, "phase", Integer.class));
+		assertSame(this.sendChannel, TestUtils.getPropertyValue(this.customAdapter, "outputChannel"));
+	}
+
+}
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests-context.xml
index b5126265a3..5041900e54 100644
--- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests-context.xml
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests-context.xml
@@ -11,7 +11,7 @@
 
 	
 
@@ -21,6 +21,12 @@
 		
 	
 
+	
+
+	
+		
+	
+
 	
 		
 	
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests.java
index 774c98eb70..9566ff5cf3 100644
--- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests.java
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests.java
@@ -25,15 +25,17 @@ import org.junit.runner.RunWith;
 import org.springframework.beans.DirectFieldAccessor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
-import org.springframework.messaging.Message;
-import org.springframework.messaging.MessageChannel;
+import org.springframework.expression.Expression;
 import org.springframework.integration.channel.QueueChannel;
 import org.springframework.integration.endpoint.EventDrivenConsumer;
-import org.springframework.messaging.support.GenericMessage;
 import org.springframework.integration.redis.outbound.RedisPublishingMessageHandler;
 import org.springframework.integration.redis.rules.RedisAvailable;
 import org.springframework.integration.redis.rules.RedisAvailableTests;
+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.support.GenericMessage;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
@@ -59,7 +61,9 @@ public class RedisOutboundChannelAdapterParserTests extends RedisAvailableTests{
 				new DirectFieldAccessor(adapter).getPropertyValue("handler");
 		assertEquals("outboundAdapter", adapter.getComponentName());
 		DirectFieldAccessor accessor = new DirectFieldAccessor(handler);
-		assertEquals("foo", accessor.getPropertyValue("defaultTopic"));
+		Object topicExpression = accessor.getPropertyValue("topicExpression");
+		assertNotNull(topicExpression);
+		assertEquals("headers['topic'] ?: 'foo'", ((Expression) topicExpression).getExpressionString());
 		Object converterBean = context.getBean("testConverter");
 		assertEquals(converterBean, accessor.getPropertyValue("messageConverter"));
 		assertEquals(context.getBean("serializer"), accessor.getPropertyValue("serializer"));
@@ -74,6 +78,13 @@ public class RedisOutboundChannelAdapterParserTests extends RedisAvailableTests{
 		Message message = receiveChannel.receive(5000);
 		assertNotNull(message);
 		assertEquals("Hello Redis", message.getPayload());
+
+		sendChannel = context.getBean("sendChannel", MessageChannel.class);
+		sendChannel.send(MessageBuilder.withPayload("Hello Redis").setHeader("topic", "bar").build());
+		receiveChannel = context.getBean("barChannel", QueueChannel.class);
+		message = receiveChannel.receive(5000);
+		assertNotNull(message);
+		assertEquals("Hello Redis", message.getPayload());
 	}
 
 	@Test //INT-2275
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParserTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParserTests-context.xml
new file mode 100644
index 0000000000..6fc343aeed
--- /dev/null
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParserTests-context.xml
@@ -0,0 +1,29 @@
+
+
+
+	
+		
+	
+
+	
+
+	
+
+	
+
+	
+
+	
+
+
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParserTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParserTests.java
new file mode 100644
index 0000000000..14dcd4ccff
--- /dev/null
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisQueueOutboundChannelAdapterParserTests.java
@@ -0,0 +1,83 @@
+/*
+ * 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.redis.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+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.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.expression.Expression;
+import org.springframework.integration.redis.outbound.RedisQueueOutboundChannelAdapter;
+import org.springframework.integration.redis.rules.RedisAvailable;
+import org.springframework.integration.redis.rules.RedisAvailableTests;
+import org.springframework.integration.test.util.TestUtils;
+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 RedisQueueOutboundChannelAdapterParserTests {
+
+	@Autowired
+	@Qualifier("redisConnectionFactory")
+	private RedisConnectionFactory connectionFactory;
+
+	@Autowired
+	@Qualifier("customRedisConnectionFactory")
+	private RedisConnectionFactory customRedisConnectionFactory;
+
+	@Autowired
+	@Qualifier("defaultAdapter.handler")
+	private RedisQueueOutboundChannelAdapter defaultAdapter;
+
+	@Autowired
+	@Qualifier("customAdapter.handler")
+	private RedisQueueOutboundChannelAdapter customAdapter;
+
+	@Autowired
+	private RedisSerializer serializer;
+
+	@Test
+	public void testInt3017DefaultConfig() {
+		assertSame(this.connectionFactory, TestUtils.getPropertyValue(this.defaultAdapter, "template.connectionFactory"));
+		assertEquals("foo", TestUtils.getPropertyValue(this.defaultAdapter, "queueNameExpression", Expression.class).getExpressionString());
+		assertTrue(TestUtils.getPropertyValue(this.defaultAdapter, "extractPayload", Boolean.class));
+		assertFalse(TestUtils.getPropertyValue(this.defaultAdapter, "serializerExplicitlySet", Boolean.class));
+	}
+
+	@Test
+	public void testInt3017CustomConfig() {
+		assertSame(this.customRedisConnectionFactory, TestUtils.getPropertyValue(this.customAdapter, "template.connectionFactory"));
+		assertEquals("headers['redis_queue']", TestUtils.getPropertyValue(this.customAdapter, "queueNameExpression", Expression.class).getExpressionString());
+		assertFalse(TestUtils.getPropertyValue(this.customAdapter, "extractPayload", Boolean.class));
+		assertTrue(TestUtils.getPropertyValue(this.customAdapter, "serializerExplicitlySet", Boolean.class));
+		assertSame(this.serializer, TestUtils.getPropertyValue(this.customAdapter, "serializer"));
+	}
+
+}
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapterTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapterTests.java
index b136d21ef7..b8f3a2e301 100644
--- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapterTests.java
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapterTests.java
@@ -18,15 +18,14 @@ package org.springframework.integration.redis.inbound;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import org.hamcrest.Matchers;
 import org.junit.Test;
 
-import org.springframework.data.redis.connection.RedisConnection;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.data.redis.listener.RedisMessageListenerContainer;
 import org.springframework.messaging.Message;
@@ -37,12 +36,11 @@ import org.springframework.integration.test.util.TestUtils;
 
 /**
  * @author Mark Fisher
+ * @author Artem Bilan
  * @since 2.1
  */
 public class RedisInboundChannelAdapterTests extends RedisAvailableTests{
 
-	private final Log logger = LogFactory.getLog(this.getClass());
-
 	@Test
 	@RedisAvailable
 	public void testRedisInboundChannelAdapter() throws Exception {
@@ -59,19 +57,18 @@ public class RedisInboundChannelAdapterTests extends RedisAvailableTests{
 		RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
 
 		RedisInboundChannelAdapter adapter = new RedisInboundChannelAdapter(connectionFactory);
-		adapter.setTopics("testRedisInboundChannelAdapterChannel");
+		adapter.setTopics(redisChannelName);
 		adapter.setOutputChannel(channel);
 		adapter.afterPropertiesSet();
 		adapter.start();
 
-		RedisMessageListenerContainer container = waitUntilSubscribed(adapter);
+		this.awaitContainerSubscribed(TestUtils.getPropertyValue(adapter, "container", RedisMessageListenerContainer.class));
 
 		StringRedisTemplate redisTemplate = new StringRedisTemplate(connectionFactory);
 		redisTemplate.afterPropertiesSet();
 		for (int i = 0; i < numToTest; i++) {
 			String message = "test-" + i + " iteration " + iteration;
 			redisTemplate.convertAndSend(redisChannelName, message);
-			logger.debug("Sent " + message);
 		}
 		int counter = 0;
 		for (int i = 0; i < numToTest; i++) {
@@ -85,34 +82,42 @@ public class RedisInboundChannelAdapterTests extends RedisAvailableTests{
 		}
 		assertEquals(numToTest, counter);
 		adapter.stop();
-		container.stop();
-	}
 
-	/**
-	 * Wait until the container has subscribed to the queue and return a
-	 * reference to it, so we can stop it at the end of the test.
-	 */
-	protected RedisMessageListenerContainer waitUntilSubscribed(
-			RedisInboundChannelAdapter adapter) throws Exception {
-		RedisMessageListenerContainer container = (RedisMessageListenerContainer) TestUtils
-				.getPropertyValue(adapter, "container");
-		Object subscriptionTask = TestUtils.getPropertyValue(container, "subscriptionTask");
-		RedisConnection connection = (RedisConnection) TestUtils
-				.getPropertyValue(subscriptionTask, "connection");
-		int n = 0;
-		while (true) {
-			if (n++ > 50) {
-				fail("RMLC Failed to Subscribe");
-			}
-			if (connection.isSubscribed()) {
-				logger.debug("Subscribed OK");
-				break;
-			}
-			logger.debug("Waiting...");
-			Thread.sleep(100);
+		redisChannelName = "testRedisBytesInboundChannelAdapterChannel";
+
+		adapter.setTopics(redisChannelName);
+		adapter.setSerializer(null);
+		adapter.afterPropertiesSet();
+		adapter.start();
+
+		this.awaitContainerSubscribed(TestUtils.getPropertyValue(adapter, "container", RedisMessageListenerContainer.class));
+
+		RedisTemplate template = new RedisTemplate();
+		template.setConnectionFactory(connectionFactory);
+		template.setEnableDefaultSerializer(false);
+		template.afterPropertiesSet();
+
+		for (int i = 0; i < numToTest; i++) {
+			String message = "test-" + i + " iteration " + iteration;
+			template.convertAndSend(redisChannelName, message.getBytes());
 		}
-		Thread.sleep(100); // Wait a little longer due to race condition in connection.isSubscribed()
-		return container;
+
+		counter = 0;
+		for (int i = 0; i < numToTest; i++) {
+			Message message = channel.receive(5000);
+			if (message == null){
+				throw new RuntimeException("Failed to receive message # " + i + " iteration " + iteration);
+			}
+			assertNotNull(message);
+			Object payload = message.getPayload();
+			assertThat(payload, Matchers.instanceOf(byte[].class));
+
+			assertTrue(new String((byte[]) payload).startsWith("test-"));
+			counter++;
+		}
+
+		assertEquals(numToTest, counter);
+		adapter.stop();
 	}
 
 }
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests-context.xml
new file mode 100644
index 0000000000..3214554326
--- /dev/null
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests-context.xml
@@ -0,0 +1,44 @@
+
+
+
+	
+		
+	
+
+	
+		
+	
+
+	
+
+	
+
+	
+		
+		
+	
+
+	
+
+
+	
+
+	
+		
+	
+
+
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests.java
index 31e1b94b7e..2709daca91 100644
--- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests.java
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests.java
@@ -18,33 +18,68 @@ package org.springframework.integration.redis.inbound;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertThat;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
+import java.util.List;
+import java.util.UUID;
 
 import org.hamcrest.Matchers;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.data.redis.RedisConnectionFailureException;
+import org.springframework.data.redis.RedisSystemException;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 import org.springframework.integration.channel.QueueChannel;
+import org.springframework.integration.event.IntegrationEvent;
+import org.springframework.integration.redis.event.RedisExceptionEvent;
 import org.springframework.integration.redis.rules.RedisAvailable;
 import org.springframework.integration.redis.rules.RedisAvailableTests;
 import org.springframework.integration.support.MessageBuilder;
 import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
 import org.springframework.messaging.MessagingException;
 import org.springframework.messaging.PollableChannel;
 import org.springframework.messaging.support.ErrorMessage;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
 /**
  * @author Gunnar Hillert
  * @author Artem Bilan
  * @since 3.0
  */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
 public class RedisQueueMessageDrivenEndpointTests extends RedisAvailableTests {
 
+	@Autowired
+	private RedisConnectionFactory connectionFactory;
+
+	@Autowired
+	private PollableChannel fromChannel;
+
+	@Autowired
+	private MessageChannel symmetricalInputChannel;
+
+	@Autowired
+	private PollableChannel symmetricalOutputChannel;
+
 	@Test
 	@RedisAvailable
 	@SuppressWarnings("unchecked")
@@ -52,10 +87,8 @@ public class RedisQueueMessageDrivenEndpointTests extends RedisAvailableTests {
 
 		String queueName = "si.test.redisQueueInboundChannelAdapterTests";
 
-		RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
-
 		RedisTemplate redisTemplate = new RedisTemplate();
-		redisTemplate.setConnectionFactory(connectionFactory);
+		redisTemplate.setConnectionFactory(this.connectionFactory);
 		redisTemplate.setEnableDefaultSerializer(false);
 		redisTemplate.setKeySerializer(new StringRedisSerializer());
 		redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
@@ -71,7 +104,8 @@ public class RedisQueueMessageDrivenEndpointTests extends RedisAvailableTests {
 
 		PollableChannel channel = new QueueChannel();
 
-		RedisQueueMessageDrivenEndpoint endpoint = new RedisQueueMessageDrivenEndpoint(queueName, connectionFactory);
+		RedisQueueMessageDrivenEndpoint endpoint = new RedisQueueMessageDrivenEndpoint(queueName, this.connectionFactory);
+		endpoint.setBeanFactory(Mockito.mock(BeanFactory.class));
 		endpoint.setOutputChannel(channel);
 		endpoint.setReceiveTimeout(1000);
 		endpoint.afterPropertiesSet();
@@ -86,7 +120,6 @@ public class RedisQueueMessageDrivenEndpointTests extends RedisAvailableTests {
 		assertEquals(payload2, receive.getPayload());
 
 		endpoint.stop();
-		this.waitUntilListening(endpoint);
 	}
 
 	@Test
@@ -96,10 +129,8 @@ public class RedisQueueMessageDrivenEndpointTests extends RedisAvailableTests {
 
 		final String queueName = "si.test.redisQueueInboundChannelAdapterTests2";
 
-		RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
-
 		RedisTemplate redisTemplate = new RedisTemplate();
-		redisTemplate.setConnectionFactory(connectionFactory);
+		redisTemplate.setConnectionFactory(this.connectionFactory);
 		redisTemplate.setEnableDefaultSerializer(false);
 		redisTemplate.setKeySerializer(new StringRedisSerializer());
 		redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
@@ -115,7 +146,8 @@ public class RedisQueueMessageDrivenEndpointTests extends RedisAvailableTests {
 
 		PollableChannel errorChannel = new QueueChannel();
 
-		RedisQueueMessageDrivenEndpoint endpoint = new RedisQueueMessageDrivenEndpoint(queueName, connectionFactory);
+		RedisQueueMessageDrivenEndpoint endpoint = new RedisQueueMessageDrivenEndpoint(queueName, this.connectionFactory);
+		endpoint.setBeanFactory(Mockito.mock(BeanFactory.class));
 		endpoint.setExpectMessage(true);
 		endpoint.setOutputChannel(channel);
 		endpoint.setErrorChannel(errorChannel);
@@ -137,21 +169,95 @@ public class RedisQueueMessageDrivenEndpointTests extends RedisAvailableTests {
 		assertThat(((Exception) receive.getPayload()).getCause().getMessage(),
 				Matchers.containsString("java.lang.String cannot be cast to org.springframework.messaging.Message"));
 
-
 		endpoint.stop();
-		this.waitUntilListening(endpoint);
 	}
 
+	@Test
+	@RedisAvailable
+	public void testInt3017IntegrationInbound() throws Exception {
 
-	public void waitUntilListening(RedisQueueMessageDrivenEndpoint endpoint) throws Exception {
-		int n = 0;
-		while (endpoint.isListening()) {
-			Thread.sleep(100);
-			if (n++ > 100) {
-				throw new Exception("RedisQueueMessageDrivenEndpoint failed to stop.");
+		String payload = new Date().toString();
+
+		RedisTemplate redisTemplate = new StringRedisTemplate();
+		redisTemplate.setConnectionFactory(this.connectionFactory);
+		redisTemplate.afterPropertiesSet();
+
+		redisTemplate.boundListOps("si.test.Int3017IntegrationInbound").leftPush("{\"payload\":\"" + payload + "\",\"headers\":{}}");
+
+		Message receive = this.fromChannel.receive(2000);
+		assertNotNull(receive);
+		assertEquals(payload, receive.getPayload());
+	}
+
+	@Test
+	@RedisAvailable
+	public void testInt3017IntegrationSymmetrical() throws Exception {
+		UUID payload = UUID.randomUUID();
+		Message message = MessageBuilder.withPayload(payload)
+				.setHeader("redis_queue", "si.test.Int3017IntegrationSymmetrical")
+				.build();
+
+		this.symmetricalInputChannel.send(message);
+
+		Message receive = this.symmetricalOutputChannel.receive(2000);
+		assertNotNull(receive);
+		assertEquals(payload, receive.getPayload());
+	}
+
+	@Test
+	@RedisAvailable
+	@SuppressWarnings("unchecked")
+	public void testInt3196Recovery() throws Exception {
+		String queueName = "test.si.Int3196Recovery";
+		QueueChannel channel = new QueueChannel();
+
+		final List exceptionEvents = new ArrayList();
+
+		RedisQueueMessageDrivenEndpoint endpoint = new RedisQueueMessageDrivenEndpoint(queueName, this.connectionFactory);
+		endpoint.setBeanFactory(Mockito.mock(BeanFactory.class));
+		endpoint.setApplicationEventPublisher(new ApplicationEventPublisher() {
+
+			@Override
+			public void publishEvent(ApplicationEvent event) {
+				exceptionEvents.add(event);
 			}
+		});
+		endpoint.setOutputChannel(channel);
+		endpoint.setReceiveTimeout(100);
+		endpoint.setRecoveryInterval(200);
+		endpoint.afterPropertiesSet();
+		endpoint.start();
+
+		((DisposableBean) this.connectionFactory).destroy();
+
+		Thread.sleep(300);
+
+		assertThat(exceptionEvents.size(), Matchers.greaterThan(0));
+		for (ApplicationEvent exceptionEvent : exceptionEvents) {
+			assertThat(exceptionEvent, Matchers.instanceOf(RedisExceptionEvent.class));
+			assertSame(endpoint, exceptionEvent.getSource());
+			assertThat(((IntegrationEvent) exceptionEvent).getCause().getClass(),
+					Matchers.isIn(Arrays.> asList(RedisSystemException.class, RedisConnectionFailureException.class)));
 		}
 
+		((InitializingBean) this.connectionFactory).afterPropertiesSet();
+
+		RedisTemplate redisTemplate = new RedisTemplate();
+		redisTemplate.setConnectionFactory(this.getConnectionFactoryForTest());
+		redisTemplate.setEnableDefaultSerializer(false);
+		redisTemplate.setKeySerializer(new StringRedisSerializer());
+		redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
+		redisTemplate.afterPropertiesSet();
+
+		String payload = "testing";
+
+		redisTemplate.boundListOps(queueName).leftPush(payload);
+
+		Message receive = channel.receive(1000);
+		assertNotNull(receive);
+		assertEquals(payload, receive.getPayload());
+
+		endpoint.stop();
 	}
 
 }
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/metadata/RedisMetadataStoreTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/metadata/RedisMetadataStoreTests.java
similarity index 89%
rename from spring-integration-redis/src/test/java/org/springframework/integration/redis/store/metadata/RedisMetadataStoreTests.java
rename to spring-integration-redis/src/test/java/org/springframework/integration/redis/metadata/RedisMetadataStoreTests.java
index 2e57b9c1b2..c9a48610c8 100644
--- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/store/metadata/RedisMetadataStoreTests.java
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/metadata/RedisMetadataStoreTests.java
@@ -13,7 +13,7 @@
  *     See the License for the specific language governing permissions and
  *     limitations under the License.
  */
-package org.springframework.integration.redis.store.metadata;
+package org.springframework.integration.redis.metadata;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -29,6 +29,7 @@ import org.springframework.integration.redis.rules.RedisAvailableTests;
 
 /**
  * @author Gunnar Hillert
+ * @author Artem Bilan
  * @since 3.0
  *
  */
@@ -143,4 +144,20 @@ public class RedisMetadataStoreTests extends RedisAvailableTests {
 
 		fail("Expected an IllegalArgumentException to be thrown.");
 	}
+
+	@Test
+	@RedisAvailable
+	public void testRemoveFromMetadataStore(){
+		RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
+		RedisMetadataStore metadataStore = new RedisMetadataStore(jcf);
+
+		String testKey = "RedisMetadataStoreTests-Remove";
+		String testValue = "Integration";
+
+		metadataStore.put(testKey, testValue);
+
+		assertEquals(testValue, metadataStore.remove(testKey));
+		assertNull(metadataStore.remove(testKey));
+	}
+
 }
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandlerTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandlerTests.java
index 71f4b97f77..dc3648cc06 100644
--- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandlerTests.java
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandlerTests.java
@@ -30,12 +30,14 @@ import org.springframework.data.redis.listener.RedisMessageListenerContainer;
 import org.springframework.data.redis.listener.Topic;
 import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.springframework.expression.common.LiteralExpression;
 import org.springframework.integration.redis.rules.RedisAvailable;
 import org.springframework.integration.redis.rules.RedisAvailableTests;
 import org.springframework.integration.support.MessageBuilder;
 
 /**
  * @author Mark Fisher
+ * @author Artem Bilan
  * @since 2.1
  */
 public class RedisPublishingMessageHandlerTests extends RedisAvailableTests {
@@ -45,7 +47,7 @@ public class RedisPublishingMessageHandlerTests extends RedisAvailableTests {
 	public void testRedisPublishingMessageHandler() throws Exception {
 		int numToTest = 10;
 		String topic = "si.test.channel";
-		final CountDownLatch latch = new CountDownLatch(numToTest);
+		final CountDownLatch latch = new CountDownLatch(numToTest * 2);
 
 		RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
 
@@ -59,14 +61,20 @@ public class RedisPublishingMessageHandlerTests extends RedisAvailableTests {
 		container.afterPropertiesSet();
 		container.addMessageListener(listener, Collections.singletonList(new ChannelTopic(topic)));
 		container.start();
-		Thread.sleep(1000);
+
+		this.awaitContainerSubscribed(container);
 
 		final RedisPublishingMessageHandler handler = new RedisPublishingMessageHandler(connectionFactory);
-		handler.setDefaultTopic(topic);
+		handler.setTopicExpression(new LiteralExpression(topic));
+
 		for (int i = 0; i < numToTest; i++) {
 			handler.handleMessage(MessageBuilder.withPayload("test-" + i).build());
 		}
-		assertTrue(latch.await(3, TimeUnit.SECONDS));
+
+		for (int i = 0; i < numToTest; i++) {
+			handler.handleMessage(MessageBuilder.withPayload(("test-" + i).getBytes()).build());
+		}
+		assertTrue(latch.await(10, TimeUnit.SECONDS));
 		container.stop();
 	}
 
@@ -83,6 +91,7 @@ public class RedisPublishingMessageHandlerTests extends RedisAvailableTests {
 		public void handleMessage(String s) {
 			this.latch.countDown();
 		}
+
 	}
 
 }
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests-context.xml b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests-context.xml
new file mode 100644
index 0000000000..87a2b1c3ae
--- /dev/null
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests-context.xml
@@ -0,0 +1,23 @@
+
+
+
+	
+		
+	
+
+	
+		
+	
+
+	
+
+
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests.java
index 0138291969..e913750610 100644
--- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests.java
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests.java
@@ -24,41 +24,58 @@ import java.util.Date;
 import java.util.concurrent.TimeUnit;
 
 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.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.data.redis.serializer.JacksonJsonRedisSerializer;
 import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.springframework.integration.mapping.InboundMessageMapper;
 import org.springframework.integration.redis.rules.RedisAvailable;
 import org.springframework.integration.redis.rules.RedisAvailableTests;
 import org.springframework.integration.support.MessageBuilder;
+import org.springframework.integration.support.json.Jackson2JsonMessageParser;
+import org.springframework.integration.support.json.JsonInboundMessageMapper;
 import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
 import org.springframework.messaging.support.GenericMessage;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
 /**
  * @author Gunnar Hillert
  * @author Artem Bilan
  * @since 3.0
  */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
 public class RedisQueueOutboundChannelAdapterTests extends RedisAvailableTests {
 
+	@Autowired
+	private RedisConnectionFactory connectionFactory;
+
+	@Autowired
+	@Qualifier("toRedisQueueChannel")
+	private MessageChannel sendChannel;
+
+
 	@Test
 	@RedisAvailable
 	public void testInt3015Default() throws Exception {
 
 		final String queueName = "si.test.testRedisQueueOutboundChannelAdapter";
 
-		RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
-
-		final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, connectionFactory);
+		final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, this.connectionFactory);
 
 		String payload = "testing";
 		handler.handleMessage(MessageBuilder.withPayload(payload).build());
 
 		RedisTemplate redisTemplate = new StringRedisTemplate();
-		redisTemplate.setConnectionFactory(connectionFactory);
+		redisTemplate.setConnectionFactory(this.connectionFactory);
 		redisTemplate.afterPropertiesSet();
 
 		Object result = redisTemplate.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS);
@@ -70,7 +87,7 @@ public class RedisQueueOutboundChannelAdapterTests extends RedisAvailableTests {
 		handler.handleMessage(MessageBuilder.withPayload(payload2).build());
 
 		RedisTemplate redisTemplate2 = new RedisTemplate();
-		redisTemplate2.setConnectionFactory(connectionFactory);
+		redisTemplate2.setConnectionFactory(this.connectionFactory);
 		redisTemplate2.setEnableDefaultSerializer(false);
 		redisTemplate2.setKeySerializer(new StringRedisSerializer());
 		redisTemplate2.setValueSerializer(new JdkSerializationRedisSerializer());
@@ -88,16 +105,14 @@ public class RedisQueueOutboundChannelAdapterTests extends RedisAvailableTests {
 
 		final String queueName = "si.test.testRedisQueueOutboundChannelAdapter2";
 
-		RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
-
-		final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, connectionFactory);
+		final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, this.connectionFactory);
 		handler.setExtractPayload(false);
 
 		Message message = MessageBuilder.withPayload("testing").build();
 		handler.handleMessage(message);
 
 		RedisTemplate redisTemplate = new RedisTemplate();
-		redisTemplate.setConnectionFactory(connectionFactory);
+		redisTemplate.setConnectionFactory(this.connectionFactory);
 		redisTemplate.setEnableDefaultSerializer(false);
 		redisTemplate.setKeySerializer(new StringRedisSerializer());
 		redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
@@ -116,13 +131,11 @@ public class RedisQueueOutboundChannelAdapterTests extends RedisAvailableTests {
 
 		final String queueName = "si.test.testRedisQueueOutboundChannelAdapter2";
 
-		RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
-
-		final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, connectionFactory);
+		final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, this.connectionFactory);
 		handler.setSerializer(new JacksonJsonRedisSerializer(Object.class));
 
 		RedisTemplate redisTemplate = new StringRedisTemplate();
-		redisTemplate.setConnectionFactory(connectionFactory);
+		redisTemplate.setConnectionFactory(this.connectionFactory);
 		redisTemplate.afterPropertiesSet();
 
 		handler.handleMessage(new GenericMessage(Arrays.asList("foo", "bar", "baz")));
@@ -140,4 +153,24 @@ public class RedisQueueOutboundChannelAdapterTests extends RedisAvailableTests {
 		assertEquals("\"test\"", result);
 	}
 
+	@Test
+	@RedisAvailable
+	public void testInt3017IntegrationOutbound() throws Exception {
+
+		final String queueName = "si.test.Int3017IntegrationOutbound";
+
+		GenericMessage message = new GenericMessage(queueName);
+		this.sendChannel.send(message);
+
+		RedisTemplate redisTemplate = new StringRedisTemplate();
+		redisTemplate.setConnectionFactory(this.connectionFactory);
+		redisTemplate.afterPropertiesSet();
+
+		String result = redisTemplate.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS);
+		assertNotNull(result);
+		InboundMessageMapper mapper = new JsonInboundMessageMapper(String.class, new Jackson2JsonMessageParser());
+		Message resultMessage = mapper.toMessage(result);
+		assertEquals(message.getPayload(), resultMessage.getPayload());
+	}
+
 }
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/rules/RedisAvailableTests.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/rules/RedisAvailableTests.java
index 064f5cf3d7..4adb288a48 100644
--- a/spring-integration-redis/src/test/java/org/springframework/integration/redis/rules/RedisAvailableTests.java
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/rules/RedisAvailableTests.java
@@ -15,6 +15,8 @@
  */
 package org.springframework.integration.redis.rules;
 
+import static org.junit.Assert.assertTrue;
+
 import java.util.UUID;
 
 import org.junit.Rule;
@@ -28,6 +30,8 @@ import org.springframework.data.redis.core.BoundZSetOperations;
 import org.springframework.data.redis.core.RedisCallback;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.listener.RedisMessageListenerContainer;
+import org.springframework.integration.test.util.TestUtils;
 
 /**
  * @author Oleg Zhurakousky
@@ -40,7 +44,7 @@ public class RedisAvailableTests {
 	@Rule
 	public RedisAvailableRule redisAvailableRule = new RedisAvailableRule();
 
-	public RedisConnectionFactory getConnectionFactoryForTest(){
+	protected RedisConnectionFactory getConnectionFactoryForTest(){
 		LettuceConnectionFactory connectionFactory =  RedisAvailableRule.connectionFactoryResource.get();
 		RedisTemplate rt = new RedisTemplate();
 		rt.setConnectionFactory(connectionFactory);
@@ -56,6 +60,17 @@ public class RedisAvailableTests {
 		return connectionFactory;
 	}
 
+	protected void awaitContainerSubscribed(RedisMessageListenerContainer container) throws Exception {
+		RedisConnection connection = TestUtils.getPropertyValue(container, "subscriptionTask.connection",
+				RedisConnection.class);
+
+		int n = 0;
+		while (n++ < 100 && !connection.isSubscribed()) {
+			Thread.sleep(100);
+		}
+		assertTrue("RedisMessageListenerContainer Failed to Subscribe", n < 100);
+	}
+
 	protected void prepareList(RedisConnectionFactory connectionFactory){
 
 		StringRedisTemplate redisTemplate = new StringRedisTemplate();
diff --git a/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/CustomJsonSerializer.java b/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/CustomJsonSerializer.java
new file mode 100644
index 0000000000..d76f24a637
--- /dev/null
+++ b/spring-integration-redis/src/test/java/org/springframework/integration/redis/util/CustomJsonSerializer.java
@@ -0,0 +1,60 @@
+/*
+ * 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.redis.util;
+
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+import org.springframework.integration.Message;
+import org.springframework.integration.mapping.InboundMessageMapper;
+import org.springframework.integration.support.json.Jackson2JsonMessageParser;
+import org.springframework.integration.support.json.JsonInboundMessageMapper;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+* @author Artem Bilan
+* @since 3.0
+*/
+public class CustomJsonSerializer implements RedisSerializer> {
+
+	private final ObjectMapper objectMapper = new ObjectMapper();
+
+	private final InboundMessageMapper mapper =
+			new JsonInboundMessageMapper(String.class, new Jackson2JsonMessageParser());
+
+	@Override
+	public byte[] serialize(Message message) throws SerializationException {
+		try {
+			return this.objectMapper.writeValueAsBytes(message);
+		}
+		catch (JsonProcessingException e) {
+			throw new SerializationException("Fail to serialize 'message' to json.", e);
+		}
+	}
+
+	@Override
+	public Message deserialize(byte[] bytes) throws SerializationException {
+		try {
+			return mapper.toMessage(new String(bytes));
+		}
+		catch (Exception e) {
+			throw new SerializationException("Fail to deserialize 'message' from json.", e);
+		}
+	}
+
+}
diff --git a/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/HeaderMatcher.java b/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/HeaderMatcher.java
index 5705fc9f92..15c647ff84 100644
--- a/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/HeaderMatcher.java
+++ b/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/HeaderMatcher.java
@@ -24,6 +24,7 @@ import org.hamcrest.Description;
 import org.hamcrest.Factory;
 import org.hamcrest.Matcher;
 import org.junit.Assert;
+
 import org.springframework.integration.EiMessageHeaderAccessor;
 import org.springframework.messaging.Message;
 import org.springframework.messaging.MessageHeaders;
@@ -36,30 +37,34 @@ import org.springframework.messaging.MessageHeaders;
  * entry:
  * 

* - *

- * ANY_HEADER_KEY = "foo";
- * ANY_HEADER_VALUE = "bar";
+ * 
+ * {@code
+ * ANY_HEADER_KEY = "foo";
+ * ANY_HEADER_VALUE = "bar";
  * assertThat(message, hasEntry(ANY_HEADER_KEY, ANY_HEADER_VALUE));
  * assertThat(message, hasEntry(ANY_HEADER_KEY, is(String.class)));
  * assertThat(message, hasEntry(ANY_HEADER_KEY, notNullValue()));
  * assertThat(message, hasEntry(ANY_HEADER_KEY, is(ANY_HEADER_VALUE)));
+ * }
  * 
*

* For multiple entries to match all: *

- *

- * Map<String, Object> expectedInHeaderMap = new HashMap<String, Object>();
+ * 
+ * {@code
+ * Map expectedInHeaderMap = new HashMap();
  * expectedInHeaderMap.put(ANY_HEADER_KEY, ANY_HEADER_VALUE);
  * expectedInHeaderMap.put(OTHER_HEADER_KEY, is(OTHER_HEADER_VALUE));
  * assertThat(message, HeaderMatcher.hasAllEntries(expectedInHeaderMap));
+ * }
  * 
* *

* For a single key: *

* - *

- * ANY_HEADER_KEY = "foo";
+ * 
+ * ANY_HEADER_KEY = "foo";
  * assertThat(message, HeaderMatcher.hasKey(ANY_HEADER_KEY));
  * 
* diff --git a/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/MapContentMatchers.java b/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/MapContentMatchers.java index 98e29142e6..4997dbca7a 100644 --- a/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/MapContentMatchers.java +++ b/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/MapContentMatchers.java @@ -33,7 +33,7 @@ import org.hamcrest.core.IsEqual; * It is possible to match a single entry by value or matcher like this: *

* - *
+ * 
  * assertThat(map, hasEntry(SOME_KEY, is(SOME_VALUE)));
  * assertThat(map, hasEntry(SOME_KEY, is(String.class)));
  * assertThat(map, hasEntry(SOME_KEY, notNullValue()));
@@ -43,16 +43,18 @@ import org.hamcrest.core.IsEqual;
  * It's also possible to match multiple entries in a map:
  * 

* - *
- * Map<String, Object> expectedInMap = new HashMap<String, Object>();
+ * 
+ * {@code
+ * Map expectedInMap = new HashMap();
  * expectedInMap.put(SOME_KEY, SOME_VALUE);
  * expectedInMap.put(OTHER_KEY, is(OTHER_VALUE));
  * assertThat(map, hasAllEntries(expectedInMap));
+ * }
  * 
* *

If you only need to verify the existence of a key:

* - *
+ * 
  * assertThat(map, hasKey(SOME_KEY));
  * 
* diff --git a/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/MockitoMessageMatchers.java b/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/MockitoMessageMatchers.java index 8cf0fca235..5700a8b5c6 100644 --- a/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/MockitoMessageMatchers.java +++ b/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/MockitoMessageMatchers.java @@ -23,6 +23,7 @@ import java.util.Map; import org.hamcrest.Matcher; import org.mockito.Mockito; + import org.springframework.messaging.Message; /** @@ -38,22 +39,26 @@ import org.springframework.messaging.Message; * With {@link Mockito#verify(Object)}: *

* - *
+ * 
+ * {@code
  * @Mock
  * MessageHandler handler;
  * ...
  * handler.handleMessage(message);
  * verify(handler).handleMessage(messageWithPayload(SOME_PAYLOAD));
  * verify(handler).handleMessage(messageWithPayload(is(SOME_CLASS)));
+ * }
  * 
*

* With {@link Mockito#when(Object)}: *

* - *
+ * 
+ * {@code
  * ...
  * when(channel.send(messageWithPayload(SOME_PAYLOAD))).thenReturn(true);
  * assertThat(channel.send(message), is(true));
+ * }
  * 
* * @author Alex Peters @@ -64,12 +69,12 @@ public class MockitoMessageMatchers { @SuppressWarnings("unchecked") public static Message messageWithPayload(Matcher payloadMatcher) { - return (Message) argThat(hasPayload(payloadMatcher)); + return argThat(hasPayload(payloadMatcher)); } @SuppressWarnings("unchecked") public static Message messageWithPayload(T payload) { - return (Message) argThat(hasPayload(payload)); + return argThat(hasPayload(payload)); } public static Message messageWithHeaderEntry(String key, Object value) { diff --git a/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/PayloadMatcher.java b/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/PayloadMatcher.java index 7947028881..c6cb07f824 100644 --- a/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/PayloadMatcher.java +++ b/spring-integration-test/src/main/java/org/springframework/integration/test/matcher/PayloadMatcher.java @@ -20,6 +20,7 @@ import org.hamcrest.Factory; import org.hamcrest.Matcher; import org.hamcrest.core.IsEqual; import org.junit.Assert; + import org.springframework.messaging.Message; /** @@ -29,17 +30,20 @@ import org.springframework.messaging.Message; * A Junit example using {@link Assert#assertThat(Object, Matcher)} could look * like this to test a payload value: *

- *

- * ANY_PAYLOAD = new BigDecimal("1.123");
- * Message<BigDecimal message = MessageBuilder.withPayload(ANY_PAYLOAD).build();
+ * 
+ * {@code
+ * ANY_PAYLOAD = new BigDecimal("1.123");
+ * Message
+ *
  * 

* An example using {@link Assert#assertThat(Object, Matcher)} delegating to * another {@link Matcher}. *

- *

- * ANY_PAYLOAD = new BigDecimal("1.123");
+ * 
+ * ANY_PAYLOAD = new BigDecimal("1.123");
  * assertThat(message, PayloadMatcher.hasPayload(is(BigDecimal.class)));
  * assertThat(message, PayloadMatcher.hasPayload(notNullValue()));
  * assertThat(message, not((PayloadMatcher.hasPayload(is(String.class))))); *
diff --git a/spring-integration-test/src/main/java/org/springframework/integration/test/util/SocketUtils.java b/spring-integration-test/src/main/java/org/springframework/integration/test/util/SocketUtils.java
index e54f27cbc4..b57d959ca7 100644
--- a/spring-integration-test/src/main/java/org/springframework/integration/test/util/SocketUtils.java
+++ b/spring-integration-test/src/main/java/org/springframework/integration/test/util/SocketUtils.java
@@ -47,7 +47,7 @@ public final class SocketUtils {
 	 * the need to use the methods of this class multiple times from within your
 	 * Spring Application Context XML file using SpEL. Of course you can do:
 	 *
-	 * 
+	 * 
 	 * {@code
 	 * ...port="#{T(org.springframework.integration.test.util.SocketUtils).findAvailableServerSocket(12000)}"
 	 * }
@@ -57,7 +57,7 @@ public final class SocketUtils {
 	 * This will be acceptable for single use, but if you need to invoke the
 	 * methods numerous time, you may instead want to do this:
 	 *
-	 * 
+	 * 
 	 * {@code
 	 * 
 	 *
diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/config/TwitterInboundChannelAdapterParser.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/config/TwitterInboundChannelAdapterParser.java
index 675c52fe9f..9538f12796 100644
--- a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/config/TwitterInboundChannelAdapterParser.java
+++ b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/config/TwitterInboundChannelAdapterParser.java
@@ -32,7 +32,7 @@ import org.springframework.util.StringUtils;
 
 /**
  * Parser for inbound Twitter Channel Adapters.
- * 
+ *
  * @author Oleg Zhurakousky
  * @since 2.0
  */
@@ -50,7 +50,10 @@ public class TwitterInboundChannelAdapterParser extends AbstractPollingInboundCh
 			BeanDefinitionBuilder templateBuilder = BeanDefinitionBuilder.genericBeanDefinition(TwitterTemplate.class);
 			builder.addConstructorArgValue(templateBuilder.getBeanDefinition());
 		}
+		builder.addConstructorArgValue(element.getAttribute(ID_ATTRIBUTE));
+
 		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "query");
+		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "metadata-store");
 		return builder.getBeanDefinition();
 	}
 
diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/AbstractTwitterMessageSource.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/AbstractTwitterMessageSource.java
index e851a0fd58..d657fc8647 100644
--- a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/AbstractTwitterMessageSource.java
+++ b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/AbstractTwitterMessageSource.java
@@ -28,9 +28,11 @@ import org.springframework.messaging.MessagingException;
 import org.springframework.integration.context.IntegrationContextUtils;
 import org.springframework.integration.context.IntegrationObjectSupport;
 import org.springframework.integration.core.MessageSource;
-import org.springframework.integration.store.metadata.MetadataStore;
-import org.springframework.integration.store.metadata.SimpleMetadataStore;
+import org.springframework.integration.metadata.MetadataStore;
+import org.springframework.integration.metadata.SimpleMetadataStore;
 import org.springframework.integration.support.MessageBuilder;
+import org.springframework.jmx.export.annotation.ManagedAttribute;
+import org.springframework.jmx.export.annotation.ManagedOperation;
 import org.springframework.social.twitter.api.DirectMessage;
 import org.springframework.social.twitter.api.Tweet;
 import org.springframework.social.twitter.api.Twitter;
@@ -44,24 +46,29 @@ import org.springframework.util.StringUtils;
  * messages when using the Twitter API. This class also handles keeping track of
  * the latest inbound message it has received and avoiding, where possible,
  * redelivery of duplicate messages. This functionality is enabled using the
- * {@link org.springframework.integration.store.MetadataStore} strategy.
+ * {@link org.springframework.integration.metadata.MetadataStore} strategy.
  *
  * @author Josh Long
  * @author Oleg Zhurakousky
  * @author Mark Fisher
  * @author Gunnar Hillert
+ * @author Artem Bilan
  *
  * @since 2.0
  */
 @SuppressWarnings("rawtypes")
 abstract class AbstractTwitterMessageSource extends IntegrationObjectSupport implements MessageSource {
 
-	private volatile long lastPollForTweet;
+	private final Twitter twitter;
+
+	private final TweetComparator tweetComparator = new TweetComparator();
+
+	private final Object lastEnqueuedIdMonitor = new Object();
+
+	private final String metadataKey;
 
 	private volatile MetadataStore metadataStore;
 
-	private volatile String metadataKey;
-
 	private final Queue tweets = new LinkedBlockingQueue();
 
 	private volatile int prefetchThreshold = 0;
@@ -70,25 +77,36 @@ abstract class AbstractTwitterMessageSource extends IntegrationObjectSupport
 
 	private volatile long lastProcessedId = -1;
 
-	private final Twitter twitter;
 
-	private final TweetComparator tweetComparator = new TweetComparator();
-
-	private final Object lastEnqueuedIdMonitor = new Object();
-
-
-	public AbstractTwitterMessageSource(Twitter twitter) {
+	public AbstractTwitterMessageSource(Twitter twitter, String metadataKey) {
 		Assert.notNull(twitter, "twitter must not be null");
+		Assert.notNull(metadataKey, "metadataKey must not be null");
 		this.twitter = twitter;
+		if (this.twitter.isAuthorized()){
+			UserOperations userOperations = this.twitter.userOperations();
+			String profileId = String.valueOf(userOperations.getProfileId());
+			if (profileId != null) {
+				metadataKey += "." + profileId;
+			}
+		}
+		this.metadataKey = metadataKey;
 	}
 
 
+	public void setMetadataStore(MetadataStore metadataStore) {
+		this.metadataStore = metadataStore;
+	}
+
+	public void setPrefetchThreshold(int prefetchThreshold) {
+		this.prefetchThreshold = prefetchThreshold;
+	}
+
 	protected Twitter getTwitter() {
 		return this.twitter;
 	}
 
 	@Override
-	protected void onInit() throws Exception{
+	protected void onInit() throws Exception {
 		super.onInit();
 		if (this.metadataStore == null) {
 			// first try to look for a 'metadataStore' in the context
@@ -100,26 +118,7 @@ abstract class AbstractTwitterMessageSource extends IntegrationObjectSupport
 				this.metadataStore = new SimpleMetadataStore();
 			}
 		}
-		StringBuilder metadataKeyBuilder = new StringBuilder();
-		if (StringUtils.hasText(this.getComponentType())) {
-			metadataKeyBuilder.append(this.getComponentType());
-		}
-		if (StringUtils.hasText(this.getComponentName())) {
-			metadataKeyBuilder.append("." + this.getComponentName());
-		}
-		else if (logger.isWarnEnabled()) {
-			logger.warn(this.getClass().getSimpleName() + " has no name. MetadataStore key might not be unique.");
-		}
 
-		if (this.twitter.isAuthorized()){
-			UserOperations userOperations = this.twitter.userOperations();
-			String profileId = String.valueOf(userOperations.getProfileId());
-			if (profileId != null) {
-				metadataKeyBuilder.append("." + profileId);
-			}
-		}
-
-		this.metadataKey = metadataKeyBuilder.toString();
 		String lastId = this.metadataStore.get(this.metadataKey);
 		// initialize the last status ID from the metadataStore
 		if (StringUtils.hasText(lastId)) {
@@ -132,16 +131,10 @@ abstract class AbstractTwitterMessageSource extends IntegrationObjectSupport
 	public Message receive() {
 		T tweet = this.tweets.poll();
 		if (tweet == null) {
-			long currentTime = System.currentTimeMillis();
-			long elapsedTime = currentTime - this.lastPollForTweet;
-			if (elapsedTime < 15000) {
-				// need to wait longer
-				return null;
-			}
 			this.refreshTweetQueueIfNecessary();
 			tweet = this.tweets.poll();
-			this.lastPollForTweet = currentTime;
 		}
+
 		if (tweet != null) {
 			this.lastProcessedId = this.getIdForTweet(tweet);
 			this.metadataStore.put(this.metadataKey, String.valueOf(this.lastProcessedId));
@@ -204,6 +197,27 @@ abstract class AbstractTwitterMessageSource extends IntegrationObjectSupport
 	}
 
 
+	/**
+	 * Remove the metadata key and the corresponding value from the Metadata Store.
+	 */
+	@ManagedOperation(description="Remove the metadata key and the corresponding value from the Metadata Store.")
+	void resetMetadataStore() {
+		synchronized(this) {
+			this.metadataStore.remove(this.metadataKey);
+			this.lastProcessedId = -1L;
+			this.lastEnqueuedId = -1L;
+		}
+	}
+
+	/**
+	 *
+	 * @return {@code -1} if lastProcessedId is not set, yet.
+	 */
+	@ManagedAttribute
+	public long getLastProcessedId() {
+		return this.lastProcessedId;
+	}
+
 	private class TweetComparator implements Comparator {
 
 		public int compare(T tweet1, T tweet2) {
@@ -230,6 +244,7 @@ abstract class AbstractTwitterMessageSource extends IntegrationObjectSupport
 				throw new IllegalArgumentException("Uncomparable Twitter objects: " + tweet1 + " and " + tweet2);
 			}
 		}
+
 	}
 
 }
diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/DirectMessageReceivingMessageSource.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/DirectMessageReceivingMessageSource.java
index deed9b2be7..758302aa6e 100644
--- a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/DirectMessageReceivingMessageSource.java
+++ b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/DirectMessageReceivingMessageSource.java
@@ -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");
  * you may not use this file except in compliance with the License.
@@ -31,14 +31,13 @@ import org.springframework.social.twitter.api.Twitter;
  */
 public class DirectMessageReceivingMessageSource extends AbstractTwitterMessageSource {
 
-	public DirectMessageReceivingMessageSource(Twitter twitter) {
-		super(twitter);
+	public DirectMessageReceivingMessageSource(Twitter twitter, String metadataKey) {
+		super(twitter, metadataKey);
 	}
 
-
 	@Override
 	public String getComponentType() {
-		return "twitter:dm-inbound-channel-adapter";  
+		return "twitter:dm-inbound-channel-adapter";
 	}
 
 	@Override
diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/MentionsReceivingMessageSource.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/MentionsReceivingMessageSource.java
index ad9175e84a..5551f3de1a 100644
--- a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/MentionsReceivingMessageSource.java
+++ b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/MentionsReceivingMessageSource.java
@@ -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");
  * you may not use this file except in compliance with the License.
@@ -30,11 +30,10 @@ import org.springframework.social.twitter.api.Twitter;
  */
 public class MentionsReceivingMessageSource extends AbstractTwitterMessageSource {
 
-	public MentionsReceivingMessageSource(Twitter twitter) {
-		super(twitter);
+	public MentionsReceivingMessageSource(Twitter twitter, String metadataKey) {
+		super(twitter, metadataKey);
 	}
 
-
 	@Override
 	public String getComponentType() {
 		return "twitter:mentions-inbound-channel-adapter";
diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSource.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSource.java
index d84ce95b3f..ae8bf4a377 100644
--- a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSource.java
+++ b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSource.java
@@ -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");
  * you may not use this file except in compliance with the License.
@@ -35,11 +35,10 @@ public class SearchReceivingMessageSource extends AbstractTwitterMessageSource {
 
-	public TimelineReceivingMessageSource(Twitter twitter) {
-		super(twitter);
+	public TimelineReceivingMessageSource(Twitter twitter, String metadataKey) {
+		super(twitter, metadataKey);
 	}
 
-
 	@Override
 	 public String getComponentType() {
-		return "twitter:inbound-channel-adapter";  
+		return "twitter:inbound-channel-adapter";
 	}
 
 	@Override
diff --git a/spring-integration-twitter/src/main/resources/org/springframework/integration/twitter/config/spring-integration-twitter-3.0.xsd b/spring-integration-twitter/src/main/resources/org/springframework/integration/twitter/config/spring-integration-twitter-3.0.xsd
index 3a72f4b8a9..c98713f305 100644
--- a/spring-integration-twitter/src/main/resources/org/springframework/integration/twitter/config/spring-integration-twitter-3.0.xsd
+++ b/spring-integration-twitter/src/main/resources/org/springframework/integration/twitter/config/spring-integration-twitter-3.0.xsd
@@ -123,7 +123,28 @@
 		
 				
 		
-		
+		
+			
+				
+					The bean id of this Polling Endpoint; the MessageSource is also registered with this id
+					plus a suffix '.source'; also used as the
+					MetaDataStore key with suffix '.' + the profileId from the authorized Twitter user.
+				
+			
+		
+		
+			
+				
+					
+						
+					
+				
+				
+					Identifies the channel the attached to this adapter, to which messages will be sent.
+				
+			
+		
+		
 		
 			
 				
@@ -136,6 +157,21 @@
 				
 			
 		
+		
+			
+				
+					Reference to a MetadataStore instance for storing metadata associated with
+					the retrieved feeds. If the implementation is persistent, it can help to
+					prevent duplicates between restarts. If shared, it can help coordinate multiple
+					instances of an adapter across different processes.
+				
+				
+					
+						
+					
+				
+			
+		
 	
 
 	
diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/config/TestReceivingMessageSourceParserTests.java b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/config/TestReceivingMessageSourceParserTests.java
index 73946ecd36..ec97511362 100644
--- a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/config/TestReceivingMessageSourceParserTests.java
+++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/config/TestReceivingMessageSourceParserTests.java
@@ -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");
  * you may not use this file except in compliance with the License.
@@ -16,7 +16,10 @@
 
 package org.springframework.integration.twitter.config;
 
+import static org.junit.Assert.assertNotNull;
+
 import org.junit.Test;
+
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
@@ -25,16 +28,13 @@ import org.springframework.integration.twitter.inbound.DirectMessageReceivingMes
 import org.springframework.integration.twitter.inbound.MentionsReceivingMessageSource;
 import org.springframework.integration.twitter.inbound.TimelineReceivingMessageSource;
 
-import static org.junit.Assert.assertNotNull;
-
 
 /**
  * @author Oleg Zhurakousky
+ * @author Gunnar Hillert
  */
 public class TestReceivingMessageSourceParserTests {
 
-	
-
 	@Test
 	public void testReceivingAdapterConfigurationAutoStartup(){
 		ApplicationContext ac = new ClassPathXmlApplicationContext("TestReceivingMessageSourceParser-context.xml", this.getClass());
@@ -47,10 +47,24 @@ public class TestReceivingMessageSourceParserTests {
 		assertNotNull(dms);
 
 		spca = ac.getBean("updateAdapter", SourcePollingChannelAdapter.class);
-		
+
 		spca = ac.getBean("updateAdapter", SourcePollingChannelAdapter.class);
 		TimelineReceivingMessageSource tms = TestUtils.getPropertyValue(spca, "source", TimelineReceivingMessageSource.class);
 		assertNotNull(tms);
 	}
 
+	@Test
+	public void testThatMessageSourcesAreRegisteredAsBeans(){
+		ApplicationContext ac = new ClassPathXmlApplicationContext("TestReceivingMessageSourceParser-context.xml", this.getClass());
+
+		MentionsReceivingMessageSource ms = ac.getBean("mentionAdapter.source", MentionsReceivingMessageSource.class);
+		assertNotNull(ms);
+
+		DirectMessageReceivingMessageSource dms = ac.getBean("dmAdapter.source", DirectMessageReceivingMessageSource.class);
+		assertNotNull(dms);
+
+		TimelineReceivingMessageSource tms = ac.getBean("updateAdapter.source", TimelineReceivingMessageSource.class);
+		assertNotNull(tms);
+	}
+
 }
diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/DirectMessageReceivingMessageSourceTests.java b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/DirectMessageReceivingMessageSourceTests.java
index b45abac0fa..85a64b9c02 100644
--- a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/DirectMessageReceivingMessageSourceTests.java
+++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/DirectMessageReceivingMessageSourceTests.java
@@ -31,7 +31,7 @@ import org.springframework.social.twitter.api.impl.TwitterTemplate;
  */
 public class DirectMessageReceivingMessageSourceTests {
 
-	
+
 	@SuppressWarnings("unchecked")
 	@Test @Ignore
 	public void demoReceiveDm() throws Exception{
@@ -40,11 +40,11 @@ public class DirectMessageReceivingMessageSourceTests {
 		pf.afterPropertiesSet();
 		Properties prop =  pf.getObject();
 		System.out.println(prop);
-		TwitterTemplate template = new TwitterTemplate(prop.getProperty("z_oleg.oauth.consumerKey"), 
-										               prop.getProperty("z_oleg.oauth.consumerSecret"), 
-										               prop.getProperty("z_oleg.oauth.accessToken"), 
+		TwitterTemplate template = new TwitterTemplate(prop.getProperty("z_oleg.oauth.consumerKey"),
+										               prop.getProperty("z_oleg.oauth.consumerSecret"),
+										               prop.getProperty("z_oleg.oauth.accessToken"),
 										               prop.getProperty("z_oleg.oauth.accessTokenSecret"));
-		DirectMessageReceivingMessageSource tSource = new DirectMessageReceivingMessageSource(template);
+		DirectMessageReceivingMessageSource tSource = new DirectMessageReceivingMessageSource(template, "foo");
 		tSource.afterPropertiesSet();
 		for (int i = 0; i < 50; i++) {
 			Message message = (Message) tSource.receive();
diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceTests.java b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceTests.java
index e01d370d5a..fe213d14f7 100644
--- a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceTests.java
+++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceTests.java
@@ -34,9 +34,9 @@ import org.junit.Test;
 
 import org.springframework.beans.factory.config.PropertiesFactoryBean;
 import org.springframework.core.io.ClassPathResource;
-import org.springframework.messaging.Message;
-import org.springframework.integration.store.metadata.SimpleMetadataStore;
+import org.springframework.integration.metadata.SimpleMetadataStore;
 import org.springframework.integration.test.util.TestUtils;
+import org.springframework.messaging.Message;
 import org.springframework.social.twitter.api.SearchMetadata;
 import org.springframework.social.twitter.api.SearchOperations;
 import org.springframework.social.twitter.api.SearchResults;
@@ -66,7 +66,7 @@ public class SearchReceivingMessageSourceTests {
 										               prop.getProperty("z_oleg.oauth.consumerSecret"),
 										               prop.getProperty("z_oleg.oauth.accessToken"),
 										               prop.getProperty("z_oleg.oauth.accessTokenSecret"));
-		SearchReceivingMessageSource tSource = new SearchReceivingMessageSource(template);
+		SearchReceivingMessageSource tSource = new SearchReceivingMessageSource(template, "foo");
 		tSource.setQuery(SEARCH_QUERY);
 		tSource.afterPropertiesSet();
 		for (int i = 0; i < 50; i++) {
@@ -84,14 +84,14 @@ public class SearchReceivingMessageSourceTests {
 	@Test
 	public void testSearchReceivingMessageSourceInit() {
 
-		final SearchReceivingMessageSource messageSource = new SearchReceivingMessageSource(new TwitterTemplate("test"));
+		final SearchReceivingMessageSource messageSource = new SearchReceivingMessageSource(new TwitterTemplate("test"), "foo");
 		messageSource.setComponentName("twitterSearchMessageSource");
 
 		final Object metadataStore = TestUtils.getPropertyValue(messageSource, "metadataStore");
 		final Object metadataKey = TestUtils.getPropertyValue(messageSource, "metadataKey");
 
 		assertNull(metadataStore);
-		assertNull(metadataKey);
+		assertNotNull(metadataKey);
 
 		messageSource.afterPropertiesSet();
 
@@ -101,7 +101,7 @@ public class SearchReceivingMessageSourceTests {
 		assertNotNull(metadataStoreInitialized);
 		assertTrue(metadataStoreInitialized instanceof SimpleMetadataStore);
 		assertNotNull(metadataKeyInitialized);
-		assertEquals("twitter:search-inbound-channel-adapter.twitterSearchMessageSource", metadataKeyInitialized);
+		assertEquals("foo", metadataKeyInitialized);
 
 		final Twitter twitter = TestUtils.getPropertyValue(messageSource, "twitter", Twitter.class);
 
@@ -123,7 +123,7 @@ public class SearchReceivingMessageSourceTests {
 		when(twitterTemplate.searchOperations()).thenReturn(so);
 		when(twitterTemplate.searchOperations().search(SEARCH_QUERY, 20, 0, 0)).thenReturn(null);
 
-		final SearchReceivingMessageSource messageSource = new SearchReceivingMessageSource(twitterTemplate);
+		final SearchReceivingMessageSource messageSource = new SearchReceivingMessageSource(twitterTemplate, "foo");
 		messageSource.setQuery(SEARCH_QUERY);
 
 		final String setQuery = TestUtils.getPropertyValue(messageSource, "query", String.class);
@@ -165,7 +165,7 @@ public class SearchReceivingMessageSourceTests {
 		SearchParameters params = new SearchParameters(SEARCH_QUERY).count(20).sinceId(0);
 		when(twitterTemplate.searchOperations().search(params)).thenReturn(results);
 
-		final SearchReceivingMessageSource messageSource = new SearchReceivingMessageSource(twitterTemplate);
+		final SearchReceivingMessageSource messageSource = new SearchReceivingMessageSource(twitterTemplate, "foo");
 
 		messageSource.setQuery(SEARCH_QUERY);
 
diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceWithRedisTests-context.xml b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceWithRedisTests-context.xml
index 1dcc01831e..b750aaf2f8 100644
--- a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceWithRedisTests-context.xml
+++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceWithRedisTests-context.xml
@@ -1,27 +1,27 @@
 
-
 
-	
+	
+		
+	
 
 	
-		
+		
 	
 
-	
+	
 		
 	
 
diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceWithRedisTests.java b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceWithRedisTests.java
index 34fb1f76b5..6e1ad27af0 100644
--- a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceWithRedisTests.java
+++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceWithRedisTests.java
@@ -19,6 +19,7 @@ package org.springframework.integration.twitter.inbound;
 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.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
@@ -35,43 +36,56 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.ImportResource;
-import org.springframework.data.redis.connection.RedisConnectionFactory;
-import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
+import org.springframework.integration.metadata.MetadataStore;
+import org.springframework.integration.redis.metadata.RedisMetadataStore;
 import org.springframework.integration.redis.rules.RedisAvailable;
 import org.springframework.integration.redis.rules.RedisAvailableTests;
-import org.springframework.integration.redis.store.metadata.RedisMetadataStore;
-import org.springframework.integration.store.metadata.MetadataStore;
 import org.springframework.integration.test.util.TestUtils;
-import org.springframework.messaging.Message;
+import org.springframework.messaging.PollableChannel;
 import org.springframework.social.twitter.api.SearchMetadata;
 import org.springframework.social.twitter.api.SearchOperations;
 import org.springframework.social.twitter.api.SearchResults;
 import org.springframework.social.twitter.api.Tweet;
+import org.springframework.social.twitter.api.UserOperations;
 import org.springframework.social.twitter.api.impl.SearchParameters;
 import org.springframework.social.twitter.api.impl.TwitterTemplate;
 
 /**
  * @author Gunnar Hillert
+ * @author Artem Bilan
  * @since 3.0
  */
 public class SearchReceivingMessageSourceWithRedisTests extends RedisAvailableTests {
 
 	private SourcePollingChannelAdapter twitterSearchAdapter;
-	private RedisConnectionFactory redisConnectionFactory;
-	private StringRedisTemplate redisTemplate;
 
-	private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+	private AbstractTwitterMessageSource twitterMessageSource;
+
+	private MetadataStore metadataStore;
+
+	private String metadataKey;
+
+	private PollableChannel tweets;
 
 	@Before
 	public void setup() {
+		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
 		context.register(SearchReceivingMessageSourceWithRedisTestsConfig.class);
 		context.registerShutdownHook();
 		context.refresh();
 
-		this.redisConnectionFactory = context.getBean(RedisConnectionFactory.class);
 		this.twitterSearchAdapter = context.getBean(SourcePollingChannelAdapter.class);
-		this.redisTemplate = new StringRedisTemplate(redisConnectionFactory);
+		this.twitterMessageSource = context.getBean(AbstractTwitterMessageSource.class);
+		this.metadataStore = context.getBean(MetadataStore.class);
+		this.tweets = context.getBean("inbound_twitter", PollableChannel.class);
+
+		this.metadataKey = TestUtils.getPropertyValue(twitterSearchAdapter, "source.metadataKey", String.class);
+
+		// There is need to set a value, not 'remove' and re-init 'twitterMessageSource'
+		this.metadataStore.put(metadataKey, "-1");
+
+		this.twitterMessageSource.afterPropertiesSet();
 	}
 
 	/**
@@ -81,51 +95,43 @@ public class SearchReceivingMessageSourceWithRedisTests extends RedisAvailableTe
 	@Test
 	@RedisAvailable
 	public void testPollForTweetsThreeResultsWithRedisMetadataStore() throws Exception {
-
-		final MetadataStore metadataStore = TestUtils.getPropertyValue(twitterSearchAdapter, "source.metadataStore", MetadataStore.class);
+		MetadataStore metadataStore = TestUtils.getPropertyValue(this.twitterSearchAdapter, "source.metadataStore", MetadataStore.class);
 		assertTrue("Exptected metadataStore to be an instance of RedisMetadataStore", metadataStore instanceof RedisMetadataStore);
+		assertSame(this.metadataStore, metadataStore);
 
-		/*
-		 * The metadataKey is automatically generated. To ensure that we use the
-		 * the correct key, we retrieve it from the adapter.
-		 */
-		final String metadataKey = TestUtils.getPropertyValue(twitterSearchAdapter, "source.metadataKey", String.class);
+		assertEquals("twitterSearchAdapter.74", metadataKey);
 
-		/*
-		 * As we had to retrieve the metadataKey from the adapter. The metdataStore
-		 * was already invoked and the id retrieved from Redis before we had a chance
-		 * to reset possibly pre-existing values.
-		 *
-		 * Rather than deleting the value, we have to set a value, because "null" values
-		 * returned from the MetadataStore are ignored by the onInit() method in
-		 * the AbstractTwitterMessageSource. */
-		redisTemplate.opsForValue().set(metadataKey, "-1");
-		assertEquals("-1", redisTemplate.opsForValue().get(metadataKey));
+		this.twitterSearchAdapter.start();
 
-		final SearchReceivingMessageSource source = TestUtils.getPropertyValue(twitterSearchAdapter, "source", SearchReceivingMessageSource.class);
-
-		/* We need to call onInit() in order to update the id from the metadataStore. */
-		source.onInit();
-
-		final Message message1 = source.receive();
-		final Message message2 = source.receive();
-		final Message message3 = source.receive();
+		assertNotNull(this.tweets.receive(10000));
+		assertNotNull(this.tweets.receive(1000));
+		assertNotNull(this.tweets.receive(1000));
 
 		/* We received 3 messages so far. When invoking receive() again the search
 		 * will return again the 3 test Tweets but as we already processed them
 		 * no message (null) is returned. */
-		final Message message4 = source.receive();
+		assertNull(this.tweets.receive(0));
 
-		assertNotNull(message1);
-		assertNotNull(message2);
-		assertNotNull(message3);
-		assertNull(message4);
-
-		final String persistedMetadataStoreValue = redisTemplate.opsForValue().get(metadataKey);
+		String persistedMetadataStoreValue = this.metadataStore.get(metadataKey);
 		assertNotNull(persistedMetadataStoreValue);
-		assertEquals("3", redisTemplate.opsForValue().get(metadataKey));
+		assertEquals("3", persistedMetadataStoreValue);
 
-		redisTemplate.delete(metadataKey);
+		this.twitterSearchAdapter.stop();
+
+		this.metadataStore.put(metadataKey, "1");
+
+		this.twitterMessageSource.afterPropertiesSet();
+
+		this.twitterSearchAdapter.start();
+
+		assertNotNull(this.tweets.receive(1000));
+		assertNotNull(this.tweets.receive(1000));
+
+		assertNull(this.tweets.receive(0));
+
+		persistedMetadataStoreValue = this.metadataStore.get(metadataKey);
+		assertNotNull(persistedMetadataStoreValue);
+		assertEquals("3", persistedMetadataStoreValue);
 	}
 
 	@Configuration
@@ -153,7 +159,15 @@ public class SearchReceivingMessageSourceWithRedisTests extends RedisAvailableTe
 			when(twitterTemplate.searchOperations()).thenReturn(so);
 			when(twitterTemplate.searchOperations().search(any(SearchParameters.class))).thenReturn(results);
 
+			when(twitterTemplate.isAuthorized()).thenReturn(true);
+
+			final UserOperations userOperations = mock(UserOperations.class);
+			when(twitterTemplate.userOperations()).thenReturn(userOperations);
+			when(userOperations.getProfileId()).thenReturn(74L);
+
 			return twitterTemplate;
 		}
+
 	}
+
 }
diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/TimelineReceivingMessageSourceTests.java b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/TimelineReceivingMessageSourceTests.java
index 8d604960db..3d59c06544 100644
--- a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/TimelineReceivingMessageSourceTests.java
+++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/TimelineReceivingMessageSourceTests.java
@@ -32,7 +32,7 @@ import org.springframework.social.twitter.api.impl.TwitterTemplate;
  */
 public class TimelineReceivingMessageSourceTests {
 
-	
+
 	@SuppressWarnings("unchecked")
 	@Test @Ignore
 	public void demoReceiveTimeline() throws Exception{
@@ -41,11 +41,11 @@ public class TimelineReceivingMessageSourceTests {
 		pf.afterPropertiesSet();
 		Properties prop =  pf.getObject();
 		System.out.println(prop);
-		TwitterTemplate template = new TwitterTemplate(prop.getProperty("z_oleg.oauth.consumerKey"), 
-										               prop.getProperty("z_oleg.oauth.consumerSecret"), 
-										               prop.getProperty("z_oleg.oauth.accessToken"), 
+		TwitterTemplate template = new TwitterTemplate(prop.getProperty("z_oleg.oauth.consumerKey"),
+										               prop.getProperty("z_oleg.oauth.consumerSecret"),
+										               prop.getProperty("z_oleg.oauth.accessToken"),
 										               prop.getProperty("z_oleg.oauth.accessTokenSecret"));
-		TimelineReceivingMessageSource tSource = new TimelineReceivingMessageSource(template);
+		TimelineReceivingMessageSource tSource = new TimelineReceivingMessageSource(template, "foo");
 		tSource.afterPropertiesSet();
 		for (int i = 0; i < 50; i++) {
 			Message message = (Message) tSource.receive();
diff --git a/src/reference/docbook/feed.xml b/src/reference/docbook/feed.xml
index 5215161a09..cb02137b39 100644
--- a/src/reference/docbook/feed.xml
+++ b/src/reference/docbook/feed.xml
@@ -62,14 +62,14 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/feed
   		Spring Integration provides a convenient mechanism to eliminate the need to worry about duplicate entries.
         Each feed entry will have a published date field. Every time a new Message is generated and sent,
         Spring Integration will store the value of the latest published date in an instance of the
-        org.springframework.integration.store.MetadataStore strategy. The MetadataStore interface is
+        org.springframework.integration.metadata.MetadataStore strategy. The MetadataStore interface is
         designed to store various types of generic meta-data (e.g., published date of the last feed entry that has been processed)
         to help components such as this Feed adapter deal with duplicates.
    	
 	
 		The default rule for locating this metadata store is as follows:
 		Spring Integration will look for a bean of type
-		org.springframework.integration.store.MetadataStore in
+		org.springframework.integration.metadata.MetadataStore in
 		the ApplicationContext. If one is found then it will be used, otherwise
 		it will create a new instance of SimpleMetadataStore
 		which is an in-memory implementation that will only persist metadata within
@@ -107,5 +107,10 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/feed
 			MetadataStore interface (e.g. JdbcMetadataStore)
 			and configure it as bean in the Application Context.
 		
+		
+			The key used to persist the latest published date is the value of the (required)
+			id attribute of the Feed Inbound Channel Adapter component plus the feedUrl
+			from the adapter's configuration.
+		
 	
 
diff --git a/src/reference/docbook/message.xml b/src/reference/docbook/message.xml
index 891786eea8..88fd49ae56 100644
--- a/src/reference/docbook/message.xml
+++ b/src/reference/docbook/message.xml
@@ -121,10 +121,11 @@
         
           When a message transitions through an application, each time it is
           mutated (e.g. by a transformer) a new message id is assigned. The message id is
-          a UUID. Beginning with Spring Integration 3.0, the default strategy
-          used for id generation is to use the com.eaio.uuid package to
-          generate Type 1 UUIDs. This is much more efficient than the previous
-          java.util.UUID.randomUUID() implementation.
+          a UUID. Beginning with Spring Integration 3.0,
+          the default strategy used for id generation is more efficient than the previous
+          java.util.UUID.randomUUID() implementation. It uses simple random
+          numbers based on a secure random seed, instead of creating a secure random
+          number each time.
         
         
           A different UUID generation strategy can be selected by declaring a bean that implements
@@ -147,11 +148,6 @@
           can be used in cases where a UUID is not really needed and a simple incrementing
           value is sufficient.
         
-        
-          The default strategy of creating Type 1 UUIDs may present security concerns for some users
-          because the UUID contains the MAC address of a network interface on the platform. For these
-          users, an alternate strategy should be selected.
-        
     
   
 
diff --git a/src/reference/docbook/redis.xml b/src/reference/docbook/redis.xml
index 42dcefeadd..1bd7c2023a 100644
--- a/src/reference/docbook/redis.xml
+++ b/src/reference/docbook/redis.xml
@@ -152,6 +152,12 @@ rt.setConnectionFactory(redisConnectionFactory);]]>
 
       Inbound adapters can subscribe to multiple topic names hence the comma-delimited set of values in the
       topics attribute.
+      
+         Inbound adapters can use a RedisSerializer to deserialize the body of Redis Messages.
+         The serializer attribute of the <int-redis:inbound-channel-adapter> can be set to an
+         empty string, which results in a null value for the RedisSerializer property.
+         In this case the raw byte[] bodies of Redis Messages are provided as the message payloads.
+      
     
 
     
@@ -178,7 +184,166 @@ rt.setConnectionFactory(redisConnectionFactory);]]> a RedisConnectionFactory which was defined with 'redisConnectionFactory' as its bean name. This example also includes the optional, custom MessageConverter (the 'testConverter' bean). + + Since Spring Integration 3.0, the <int-redis:outbound-channel-adapter>, + as an alternative to the topic attribute, has the topic-expression attribute to determine + the Redis topic against the Message at runtime. These attributes are mutually exclusive. +
+
+ Redis Queue Inbound Channel Adapter + + Since Spring Integration 3.0, a Queue Inbound Channel Adapter + is available to 'right pop' messages from a Redis List. + The adapter is message-driven using an internal listener thread and does not use a poller. + ]]> + + + + + The component bean name. If the channel attribute isn't provided a DirectChannel + is created and registered with application context with this id attribute as the bean name. + In this case, the endpoint itself is registered with the bean name id + '.adapter'. + + + + + The MessageChannel to which to send Messages from this Endpoint. + + + + + A SmartLifecycle attribute to specify whether this Endpoint should start automatically after + the application context start or not. Default is true. + + + + + A SmartLifecycle attribute to specify the phase in which + this Endpoint will be started. Default is 0. + + + + + A reference to a RedisConnectionFactory bean. Defaults to + redisConnectionFactory. + + + + + The name of the Redis List on which the queue-based 'right pop' operation is performed to get Redis messages. + + + + + The MessageChannel to which to send ErrorMessages with + Exceptions from the listening task of the Endpoint. + + + + + The RedisSerializer bean reference. Can be an empty string, which means 'no serializer'. + In this case the raw byte[] from the inbound Redis message is sent to the channel as the + Message payload. By default it is a JdkSerializationRedisSerializer. + + + + + The timeout in milliseconds for 'right pop' operation to wait for a Redis message from the queue. Default is 1 second. + + + + + Specify if this Endpoint expects data from the Redis queue to contain entire Messages. + If this attribute is set to true, the serializer can't be an empty string because messages + require some form of deserialization (JDK serialization by default). + Default is false. + + + + + A reference to a Spring TaskExecutor (or standard JDK 1.5+ Executor) + bean. It is used for the underlying listening task. By default a SimpleAsyncTaskExecutor + is used. + + + + +
+
+ Redis Queue Outbound Channel Adapter + + Since Spring Integration 3.0, a Queue Outbound Channel Adapter + is available to 'left push' to a Redis List from Spring Integration messages: + ]]> + + + + + The component bean name. If the channel attribute isn't provided, a DirectChannel + is created and registered with the application context with this id attribute as the bean name. + In this case, the endpoint is registered with the bean name id + '.adapter'. + + + + + The MessageChannel from which this Endpoint receives Messages. + + + + + A reference to a RedisConnectionFactory bean. Defaults to + redisConnectionFactory. + + + + + The name of the Redis List on which the queue-based 'left push' operation is performed to send Redis messages. + This attribute is mutually exclusive with queue-expression. + + + + + A SpEL Expression to determine the name of the Redis List + using the incoming Message at runtime as the #root variable. + This attribute is mutually exclusive with queue. + + + + + A RedisSerializer bean reference. + By default it is a JdkSerializationRedisSerializer. + However, for String payloads, a StringRedisSerializer + is used, if a serializer reference isn't provided. + + + + + Specify if this Endpoint should send just the payload to the Redis queue, + or the entire Message. + Default is true + . + + + + +
@@ -423,4 +588,4 @@ the serialization of values, you may want to consider providing your own
- \ No newline at end of file + diff --git a/src/reference/docbook/transformer.xml b/src/reference/docbook/transformer.xml index ee1a6d87e4..495664b267 100644 --- a/src/reference/docbook/transformer.xml +++ b/src/reference/docbook/transformer.xml @@ -184,10 +184,33 @@ public class Kid {    // setters and getters are omitted }]]> + + If you need to create a "structured" map, you can provide the 'flatten' attribute. The default value for this + attribute is 'true' meaning the default behavior; if you provide a 'false' value, then the structure will be a + map of maps. + + + For example: + nickNames; + // setters and getters are omitted +}]]> + ... will be transformed to a Map which looks like this: + {name=George, child={name=Jenna, nickNames=[Bimbo, ...]}} + To configure these transformers, Spring Integration provides namespace support Object-to-Map: ]]> + or +]]> Map-to-Object - - Previous versions of Spring Integration were dependent upon the Twitter4J API, - but with the release of Spring Social 1.0 GA, - Spring Integration, as of version 2.1, now builds directly upon Spring Social's Twitter support, instead of Twitter4J. - + + Versions of Spring Integration prior to 2.1 were dependent upon the Twitter4J API, + but with the release of Spring Social 1.0 GA, + Spring Integration, as of version 2.1, now builds directly upon Spring Social's Twitter support, instead of Twitter4J. + @@ -39,9 +39,9 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/twitter The Twitter API allows for both authenticated and anonymous operations. For authenticated operations Twitter uses OAuth - an authentication protocol that allows users to approve an application to act on their behalf without - sharing their password. More information can be found at http://oauth.net/ or - in this article http://hueniverse.com/oauth/ from Hueniverse. - Please also see OAuth FAQ for more information about OAuth and Twitter. + sharing their password. More information can be found at http://oauth.net or + in this article http://hueniverse.com/oauth from Hueniverse. + Please also see OAuth FAQ for more information about OAuth and Twitter. In order to use OAuth authentication/authorization with Twitter you must create a new Application on the Twitter Developers site. @@ -50,7 +50,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/twitter - Go to http://dev.twitter.com/ + Go to http://dev.twitter.com Click on the Register an app link and fill out all required fields on the form provided; @@ -121,31 +121,39 @@ twitter.oauth.accessTokenSecret=AbRxUAvyNCtqQtxFK8w5ZMtMj20KFhB6o]]>Twitter Inbound Adapters Twitter inbound adapters allow you to receive Twitter Messages. There are several types of - twitter messages, or tweets + twitter messages, or tweets Spring Integration version 2.0 and above provides support for receiving tweets as Timeline Updates, Direct Messages, Mention Messages as well as Search Results. - - Every Inbound Twitter Channel Adapter is a Polling Consumer which means you have to provide a poller - configuration. However, there is one important thing you must understand about Twitter since its inner-workings are slightly - different than other polling consumers. Twitter defines a concept of Rate Limiting. You can read more about - it here: Rate Limiting. In a nutshell, Rate Limiting - is the way Twitter manages how often an application can poll for updates. You should consider this when setting your - poller intervals, but we are also doing a few things to limit excessively aggressive polling within our adapters. - - + + + Every Inbound Twitter Channel Adapter is a Polling Consumer which means you have to provide a poller + configuration. + Twitter defines a concept of Rate Limiting. You can read more + about it here: Rate Limiting. In a nutshell, + Rate Limiting is a mechanism that Twitter uses to manage how often an application can poll for updates. You should consider this when + setting your poller intervals so that the adapter polls in compliance with the Twitter policies. + + + With Spring Integration prior to version 3.0, a hard-coded limit within the adapters was used to ensure + the polling interval could not be less than 15 seconds. This is no longer the case and the poller configuration is + applied directly. + + + Another issue that we need to worry about is handling duplicate Tweets. The same adapter (e.g., Search or Timeline Update) while polling on Twitter may receive the same values more than once. For example if you keep searching on Twitter with the same search criteria you'll end up with the same set of tweets unless some other new tweet that matches your search criteria was posted in between your searches. In that situation you'll get all the tweets you had before plus the new one. But what you really want is only the new tweet(s). Spring Integration provides an elegant mechanism for handling these situations. - The latest Tweet timestamp will be stored in an instance of the org.springframework.integration.store.MetadataStore which is a + The latest Tweet id will be stored in an instance of the org.springframework.integration.metadata.MetadataStore which is a strategy interface designed for storing various types of metadata (e.g., last retrieved tweet in this case). That strategy helps components such as these Twitter adapters avoid duplicates. By default, Spring Integration will look for a bean of type - org.springframework.integration.store.MetadataStore in the ApplicationContext. - If one is found then it will be used, otherwise it will create a new instance of SimpleMetadataStore + org.springframework.integration.metadata.MetadataStore in the ApplicationContext. Alternatively, + you can configure an explicit MetadataStore on the adapter. + If there is no explicit or default store, the adapter will create a new instance of SimpleMetadataStore which is a simple in-memory implementation that will only persist metadata within the lifecycle of the currently running application context. That means upon restart you may end up with duplicate entries. If you need to persist metadata between Application Context restarts, you may use the PropertiesPersistingMetadataStore (which is backed by a properties file, and a persister @@ -165,8 +173,16 @@ twitter.oauth.accessTokenSecret=AbRxUAvyNCtqQtxFK8w5ZMtMj20KFhB6o]]> ]]> -The Poller that is configured as part of any Inbound Twitter Adapter (see below) will simply poll from this MetadataStore to determine the latest tweet -received. + + If the MetadataStore is persistent, during initialization, any Inbound Twitter Adapter (see below) + will retrieve the latest tweet id that has already been sent by the adapter. + + + The key used to persist the latest twitter id is the value of the (required) + id attribute of the Twitter Inbound Channel Adapter component plus the profileId + of the Twitter user. + +
Inbound Message Channel Adapter diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 8bd71c43ee..3a6935d225 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -143,12 +143,12 @@ For more information see .
-
- Redis Metadata Store +
+ Redis: New Components A new Redis-based MetadataStore - implementation was added. The RedisMetadataStore can + implementation has been added. The RedisMetadataStore can be used to maintain state of a MetadataStore across application restarts. This new MetadataStore implementation can be used with adapters such as: @@ -158,7 +158,12 @@ Feed Inbound Channel Adapter - For more information see . + New queue-based components have been added. The <int-redis:queue-inbound-channel-adapter/> + and the <int-redis:queue-outbound-channel-adapter/> components are provided + to perform 'right pop' and 'left push' operations on a Redis List, respectively. + + + For more information see .
@@ -462,8 +467,8 @@ Message ID Generation Previously, message ids were generated using the JDK UUID.randomUUID() method. With this - release, the default mechanism has been changed to use the com.eaio.uuid package which - generates Type 1 UUIDs, and is significantly faster. In addition, the ability to change + release, the default mechanism has been changed to use a more efficient algorithm which + is significantly faster. In addition, the ability to change the strategy used to generate message ids has been added. For more information see . @@ -567,5 +572,23 @@ result set respectively. For more information see .
+
+ Redis Adapters Changers + + + + The Redis Inbound Channel Adapter can now use a null value for serializer + property, with the raw data being the message payload. + + + The Redis Outbound Channel Adapter now has the topic-expression property to determine + the Redis topic against the Message at runtime. + + + + + For more information, see . + +