Merge remote-tracking branch 'upstream/master' into 4.0.0-WIP
Conflicts: spring-integration-core/src/main/java/org/springframework/integration/MessageHeaders.java spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java spring-integration-core/src/main/java/org/springframework/integration/mapping/AbstractHeaderMapper.java spring-integration-core/src/main/java/org/springframework/integration/metadata/package-info.java spring-integration-feed/src/test/java/org/springframework/integration/feed/inbound/FeedEntryMessageSourceTests.java spring-integration-redis/src/main/java/org/springframework/integration/redis/inbound/RedisInboundChannelAdapter.java spring-integration-redis/src/main/java/org/springframework/integration/redis/outbound/RedisPublishingMessageHandler.java spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests.java spring-integration-redis/src/test/java/org/springframework/integration/redis/inbound/RedisQueueMessageDrivenEndpointTests.java spring-integration-redis/src/test/java/org/springframework/integration/redis/outbound/RedisQueueOutboundChannelAdapterTests.java spring-integration-test/src/main/java/org/springframework/integration/test/matcher/HeaderMatcher.java spring-integration-test/src/main/java/org/springframework/integration/test/matcher/MockitoMessageMatchers.java spring-integration-test/src/main/java/org/springframework/integration/test/matcher/PayloadMatcher.java spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceTests.java spring-integration-twitter/src/test/java/org/springframework/integration/twitter/inbound/SearchReceivingMessageSourceWithRedisTests.java Resolved.
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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}.<br>
|
||||
* 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
|
||||
* <pre class="code">
|
||||
* MessageBuilder.withPayload("foo").setHeader("key1", "value1").setHeader("key2", "value2");
|
||||
* </pre>
|
||||
* or create an instance of GenericMessage passing payload as {@link Object} and headers as a regular {@link Map}
|
||||
* <pre class="code">
|
||||
* Map headers = new HashMap();
|
||||
* headers.put("key1", "value1");
|
||||
* headers.put("key2", "value2");
|
||||
* new GenericMessage("foo", headers);
|
||||
* </pre>
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public final class MessageHeaders implements Map<String, Object>, 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 <b>except</b> 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<String, Object> headers;
|
||||
|
||||
|
||||
public MessageHeaders(Map<String, Object> headers) {
|
||||
this.headers = (headers != null) ? new HashMap<String, Object>(headers) : new HashMap<String, Object>();
|
||||
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> T get(Object key, Class<T> 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<Map.Entry<String, Object>> 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<String> keySet() {
|
||||
return Collections.unmodifiableSet(this.headers.keySet());
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return this.headers.size();
|
||||
}
|
||||
|
||||
public Collection<Object> 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<? extends String, ? extends Object> 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<String> keysToRemove = new ArrayList<String>();
|
||||
for (Map.Entry<String, Object> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -52,13 +52,15 @@ import org.springframework.util.Assert;
|
||||
* the configuration by removing channels that can be created implicitly.
|
||||
* <p>
|
||||
*
|
||||
* <pre>
|
||||
* <chain>
|
||||
* <filter ref="someFilter"/>
|
||||
* <bean class="SomeMessageHandlerImplementation"/>
|
||||
* <transformer ref="someTransformer"/>
|
||||
* <aggregator ... />
|
||||
* </chain>
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* <chain>
|
||||
* <filter ref="someFilter"/>
|
||||
* <bean class="SomeMessageHandlerImplementation"/>
|
||||
* <transformer ref="someTransformer"/>
|
||||
* <aggregator ... />
|
||||
* </chain>
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Mark Fisher
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Support classes for mapping.
|
||||
*/
|
||||
package org.springframework.integration.mapping.support;
|
||||
@@ -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 <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
*/
|
||||
@ManagedAttribute
|
||||
String remove(String key);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
* Provides classes supporting metadata stores.
|
||||
*/
|
||||
package org.springframework.integration.store.metadata;
|
||||
package org.springframework.integration.metadata;
|
||||
@@ -27,11 +27,13 @@ import org.springframework.integration.core.MessageSelector;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* <recipient-list-router id="simpleRouter" input-channel="routingChannelA">
|
||||
* <recipient channel="channel1"/>
|
||||
* <recipient channel="channel2"/>
|
||||
* </recipient-list-router>
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* <recipient-list-router id="simpleRouter" input-channel="routingChannelA">
|
||||
* <recipient channel="channel1"/>
|
||||
* <recipient channel="channel2"/>
|
||||
* </recipient-list-router>
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* A Message Router that sends Messages to a list of recipient channels. The
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -2169,6 +2169,15 @@
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="specialized-transformer-type">
|
||||
<xsd:attributeGroup ref="inputOutputChannelGroup" />
|
||||
<xsd:attribute name="flatten" type="xsd:string" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specifies if the result Map of Maps should be transformed further to flat keys of
|
||||
object's property paths.
|
||||
Default is 'true'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
@@ -15,4 +15,12 @@
|
||||
|
||||
<object-to-map-transformer input-channel="directInput" output-channel="output"/>
|
||||
|
||||
<channel id="nestedInput"/>
|
||||
|
||||
<channel id="nestedOutput">
|
||||
<queue capacity="1"/>
|
||||
</channel>
|
||||
|
||||
<object-to-map-transformer input-channel="nestedInput" output-channel="nestedOutput" flatten="false"/>
|
||||
|
||||
</beans:beans>
|
||||
|
||||
@@ -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<Employee> message = MessageBuilder.withPayload(employee).build();
|
||||
nestedInput.send(message);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<Map<String, Object>> outputMessage = (Message<Map<String, Object>>) nestedOutput.receive(1000);
|
||||
Map<String, Object> 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();
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<SyndEntry> {
|
||||
@@ -62,7 +63,7 @@ public class FeedEntryMessageSource extends IntegrationObjectSupport implements
|
||||
|
||||
private final Queue<SyndEntry> entries = new ConcurrentLinkedQueue<SyndEntry>();
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,28 @@
|
||||
<xsd:sequence>
|
||||
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="integration:channelAdapterAttributes"/>
|
||||
<xsd:attribute name="id" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
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.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.MessageChannel" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Identifies the channel attached to this adapter, to which messages will be sent.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="integration:smartLifeCycleAttributeGroup"/>
|
||||
<xsd:attribute name="url" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
@@ -54,7 +75,7 @@
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.store.MetadataStore" />
|
||||
<tool:expected-type type="org.springframework.integration.metadata.MetadataStore" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
|
||||
@@ -21,6 +21,6 @@
|
||||
|
||||
<bean id="fileUrlFeedFetcher" class="org.springframework.integration.feed.inbound.FileUrlFeedFetcher"/>
|
||||
|
||||
<bean id="metadataStore" class="org.springframework.integration.store.metadata.PropertiesPersistingMetadataStore"/>
|
||||
<bean id="metadataStore" class="org.springframework.integration.metadata.PropertiesPersistingMetadataStore"/>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-feed="http://www.springframework.org/schema/integration/feed"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/feed http://www.springframework.org/schema/integration/feed/spring-integration-feed.xsd">
|
||||
|
||||
<int-feed:inbound-channel-adapter channel="feedChannelUsage"
|
||||
url="file:src/test/java/org/springframework/integration/feed/sample.rss"
|
||||
feed-fetcher="fileUrlFeedFetcher">
|
||||
<int:poller fixed-rate="10000" max-messages-per-poll="100"/>
|
||||
</int-feed:inbound-channel-adapter>
|
||||
|
||||
<int:service-activator id="sampleActivator" input-channel="feedChannelUsage">
|
||||
<bean class="org.springframework.integration.feed.config.FeedInboundChannelAdapterParserTests$SampleServiceNoHistory" />
|
||||
</int:service-activator>
|
||||
|
||||
<bean id="fileUrlFeedFetcher" class="org.springframework.integration.feed.inbound.FileUrlFeedFetcher"/>
|
||||
|
||||
</beans>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<SyndEntry> 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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <pre>
|
||||
* <pre class="code">
|
||||
* INSERT INTO FOOS (MESSAGE_ID, PAYLOAD) VALUES (:headers[id], :payload)
|
||||
* </pre>
|
||||
*
|
||||
@@ -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<? extends Map<String, Object>> keys = executeUpdateQuery(message, keysGenerated);
|
||||
|
||||
@@ -76,12 +76,6 @@ import org.springframework.util.StringUtils;
|
||||
* This message store shall be used for message channels only.
|
||||
* </p>
|
||||
* <p>
|
||||
* <strong>NOTICE</strong>: 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.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* As such, the {@link JdbcChannelMessageStore} uses database specific SQL queries.
|
||||
* </p>
|
||||
* <p>
|
||||
@@ -89,8 +83,8 @@ import org.springframework.util.StringUtils;
|
||||
* database table only. The SQL scripts to create the necessary table are packaged
|
||||
* under <code>org/springframework/integration/jdbc/messagestore/channel/schema-*.sql</code>,
|
||||
* where <code>*</code> denotes the target database type.
|
||||
* </p
|
||||
* >
|
||||
* </p>
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
* @author Artem Bilan
|
||||
* @since 2.2
|
||||
@@ -331,7 +325,7 @@ public class JdbcChannelMessageStore extends AbstractMessageGroupStore implement
|
||||
* <p>For this to work, you must setup the corresponding
|
||||
* {@link TransactionSynchronizationFactory}:</p>
|
||||
*
|
||||
* <pre>
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* <int:transaction-synchronization-factory id="syncFactory">
|
||||
* <int:after-commit expression="@jdbcChannelMessageStore.removeFromIdCache(headers.id.toString())" />
|
||||
@@ -343,7 +337,7 @@ public class JdbcChannelMessageStore extends AbstractMessageGroupStore implement
|
||||
* This {@link TransactionSynchronizationFactory} is then referenced in the
|
||||
* transaction configuration of the poller:
|
||||
*
|
||||
* <pre>
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* <int:poller fixed-delay="300" receive-timeout="500"
|
||||
* max-messages-per-poll="1" task-executor="pool">
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
http://www.springframework.org/schema/integration/jmx/spring-integration-jmx.xsd">
|
||||
|
||||
<context:mbean-server id="mbs" />
|
||||
|
||||
|
||||
<context:mbean-export server="mbs" default-domain="test.PollingAdapterMBean"/>
|
||||
|
||||
<int:channel id="testChannel" />
|
||||
|
||||
<int:inbound-channel-adapter channel="testChannel" method="get">
|
||||
|
||||
<int:inbound-channel-adapter id="adapter" channel="testChannel" method="get">
|
||||
<int:poller fixed-rate="5000" max-messages-per-poll="1"/>
|
||||
<bean class="org.springframework.integration.jmx.config.PollingAdapterMBeanTests$Source"/>
|
||||
</int:inbound-channel-adapter>
|
||||
|
||||
|
||||
<int:logging-channel-adapter channel="testChannel"/>
|
||||
|
||||
|
||||
<jmx:mbean-export id="integrationMbeanExporter" server="mbs" default-domain="test.PollingAdapterMBean"/>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <outbound-channel-adapter/>} 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Events generated by the redis module
|
||||
*/
|
||||
package org.springframework.integration.redis.event;
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, byte[]> 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<Object> 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());
|
||||
}
|
||||
|
||||
@@ -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<String, String> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Provides support for Redis-based
|
||||
* {@link org.springframework.integration.metadata.MetadataStore}s.
|
||||
*/
|
||||
package org.springframework.integration.redis.metadata;
|
||||
@@ -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<Object, Object>();
|
||||
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<Object>) this.serializer).serialize(value));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* Provides support for Redis-based
|
||||
* {@link org.springframework.integration.store.metadata.MetadataStore}s.
|
||||
*/
|
||||
package org.springframework.integration.redis.store.metadata;
|
||||
@@ -82,10 +82,7 @@
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A reference to a Spring TaskExecutor (or standard JDK 1.5+ Executor) for executing
|
||||
JMS listener invokers. Default is a SimpleAsyncTaskExecutor in case of a
|
||||
DefaultMessageListenerContainer, using internally managed threads. For a
|
||||
SimpleMessageListenerContainer, listeners will always get invoked within the
|
||||
JMS provider's receive thread by default.
|
||||
Redis listener invokers. Default is a SimpleAsyncTaskExecutor.
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
@@ -177,7 +174,9 @@
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
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.
|
||||
</xsd:documentation>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.data.redis.serializer.RedisSerializer"/>
|
||||
@@ -200,7 +199,22 @@
|
||||
<xsd:sequence>
|
||||
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="topic" type="xsd:string"/>
|
||||
<xsd:attribute name="topic" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Specifies the Redis topic.
|
||||
This attribute is mutually exclusive with the 'topic-expression' attribute.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="topic-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Specifies the SpEL expression to determine the Redis topic using the Message at runtime.
|
||||
This attribute is mutually exclusive with the 'topic' attribute.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="message-converter" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
@@ -329,6 +343,146 @@
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="queue-inbound-channel-adapter">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Defines a Message Driven Endpoint for listening a Redis queue.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="redisAdapterType">
|
||||
<xsd:attribute name="queue" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Redis queue name.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="error-channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Identifies the channel to which error messages will be sent if a failure occurs in this
|
||||
Endpoint's process. If no "error-channel" reference is provided, this Endpoint will
|
||||
propagate Exceptions to the caller. To completely suppress Exceptions, provide a
|
||||
reference to the "nullChannel" here.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="serializer" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
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.
|
||||
</xsd:documentation>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.data.redis.serializer.RedisSerializer"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="receive-timeout" type="xsd:string" default="1000">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specify the timeout in milliseconds to wait for the result of the
|
||||
'rightPop' operation on Redis queue.
|
||||
Default is 1 second.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="expect-message" type="xsd:string" default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
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'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="task-executor" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A reference to a Spring TaskExecutor (or standard JDK 1.5+ Executor) for executing
|
||||
the listening task on the Redis queue. Default is a SimpleAsyncTaskExecutor.
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="java.util.concurrent.Executor"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="queue-outbound-channel-adapter">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Defines an outbound Redis Queue Message-sending Channel Adapter.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="redisAdapterType">
|
||||
<xsd:sequence>
|
||||
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="queue" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Specifies the name of the Redis queue.
|
||||
This attribute is mutually exclusive with 'queue-expression' attribute.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="queue-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Specifies the expression to determine the name of the Redis queue
|
||||
against the Message at runtime.
|
||||
This attribute is mutually exclusive with 'queue' attribute.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="order" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Specifies the order for invocation when this adapter is connected as a
|
||||
subscriber to a SubscribableChannel.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="serializer" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Reference to an instance of org.springframework.data.redis.serializer.RedisSerializer
|
||||
</xsd:documentation>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.data.redis.serializer.RedisSerializer"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="extract-payload" type="xsd:string" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specifies if the Message payload or the entire (serialized) Message will be send to the Redis queue.
|
||||
Default is 'true'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:complexType name="redisAdapterType">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -32,4 +32,7 @@
|
||||
|
||||
<int:bridge input-channel="autoChannel" output-channel="nullChannel"/>
|
||||
|
||||
<int-redis:inbound-channel-adapter id="withoutSerializer" topics="foo" serializer=""/>
|
||||
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-redis="http://www.springframework.org/schema/integration/redis"
|
||||
xmlns:task="http://www.springframework.org/schema/task"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/redis http://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd
|
||||
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
|
||||
|
||||
<bean id="redisConnectionFactory"
|
||||
class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
|
||||
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="customRedisConnectionFactory" parent="redisConnectionFactory"/>
|
||||
|
||||
<int-redis:queue-inbound-channel-adapter id="defaultAdapter" queue="si.test.Int3017.Inbound1"/>
|
||||
|
||||
<int:channel id="sendChannel"/>
|
||||
|
||||
<int-redis:queue-inbound-channel-adapter id="customAdapter"
|
||||
queue="si.test.Int3017.Inbound2"
|
||||
channel="sendChannel"
|
||||
connection-factory="customRedisConnectionFactory"
|
||||
expect-message="true"
|
||||
serializer="serializer"
|
||||
error-channel="errorChannel"
|
||||
receive-timeout="2000"
|
||||
task-executor="executor"
|
||||
auto-startup="false"
|
||||
phase="100"/>
|
||||
|
||||
<bean id="executor" class="org.springframework.integration.util.ErrorHandlingTaskExecutor">
|
||||
<constructor-arg ref="threadPoolTaskExecutor"/>
|
||||
<constructor-arg value="#{T(org.springframework.scheduling.support.TaskUtils).LOG_AND_SUPPRESS_ERROR_HANDLER}"/>
|
||||
</bean>
|
||||
|
||||
<task:executor id="threadPoolTaskExecutor" pool-size="5"/>
|
||||
|
||||
<bean id="serializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
|
||||
|
||||
</beans>
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<int-redis:outbound-channel-adapter id="outboundAdapter"
|
||||
channel="sendChannel"
|
||||
topic="foo"
|
||||
topic-expression="headers['topic'] ?: 'foo'"
|
||||
message-converter="testConverter"
|
||||
serializer="serializer"/>
|
||||
|
||||
@@ -21,6 +21,12 @@
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
|
||||
<int-redis:inbound-channel-adapter channel="barChannel" topics="bar"/>
|
||||
|
||||
<int:channel id="barChannel">
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
|
||||
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
|
||||
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
|
||||
</bean>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-redis="http://www.springframework.org/schema/integration/redis"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/redis http://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd">
|
||||
|
||||
<bean id="redisConnectionFactory"
|
||||
class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
|
||||
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="customRedisConnectionFactory" parent="redisConnectionFactory"/>
|
||||
|
||||
<int:channel id="sendChannel"/>
|
||||
|
||||
<int-redis:queue-outbound-channel-adapter id="defaultAdapter" channel="sendChannel" queue="foo"/>
|
||||
|
||||
<int-redis:queue-outbound-channel-adapter id="customAdapter" channel="sendChannel"
|
||||
queue-expression="headers['redis_queue']"
|
||||
extract-payload="false"
|
||||
serializer="serializer"
|
||||
connection-factory="customRedisConnectionFactory"/>
|
||||
|
||||
<bean id="serializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
|
||||
|
||||
</beans>
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Object, Object>();
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-redis="http://www.springframework.org/schema/integration/redis"
|
||||
xmlns:task="http://www.springframework.org/schema/task"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/redis http://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd
|
||||
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
|
||||
|
||||
<bean id="redisConnectionFactory"
|
||||
class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
|
||||
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
|
||||
</bean>
|
||||
|
||||
<int:channel id="fromChannel">
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
|
||||
<int-redis:queue-inbound-channel-adapter queue="si.test.Int3017IntegrationInbound"
|
||||
channel="fromChannel"
|
||||
expect-message="true"
|
||||
serializer="testSerializer"/>
|
||||
|
||||
<bean id="testSerializer" class="org.springframework.integration.redis.util.CustomJsonSerializer"/>
|
||||
|
||||
<int:chain input-channel="symmetricalInputChannel">
|
||||
<int:payload-serializing-transformer/>
|
||||
<int-redis:queue-outbound-channel-adapter queue-expression="headers.redis_queue"/>
|
||||
</int:chain>
|
||||
|
||||
<int-redis:queue-inbound-channel-adapter queue="si.test.Int3017IntegrationSymmetrical"
|
||||
channel="symmetricalRedisChannel"
|
||||
serializer=""/>
|
||||
|
||||
|
||||
<int:payload-deserializing-transformer input-channel="symmetricalRedisChannel" output-channel="symmetricalOutputChannel"/>
|
||||
|
||||
<int:channel id="symmetricalOutputChannel">
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
|
||||
</beans>
|
||||
@@ -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<String, Object> redisTemplate = new RedisTemplate<String, Object>();
|
||||
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<String, Object> redisTemplate = new RedisTemplate<String, Object>();
|
||||
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<String, String> 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<UUID> 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<ApplicationEvent> exceptionEvents = new ArrayList<ApplicationEvent>();
|
||||
|
||||
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.<Class<? extends Throwable>> asList(RedisSystemException.class, RedisConnectionFailureException.class)));
|
||||
}
|
||||
|
||||
((InitializingBean) this.connectionFactory).afterPropertiesSet();
|
||||
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.<Topic>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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-redis="http://www.springframework.org/schema/integration/redis"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/redis http://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd">
|
||||
|
||||
<bean id="redisConnectionFactory"
|
||||
class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
|
||||
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
|
||||
</bean>
|
||||
|
||||
<int:chain input-channel="toRedisQueueChannel">
|
||||
<int-redis:queue-outbound-channel-adapter queue-expression="payload"
|
||||
extract-payload="false"
|
||||
serializer="testSerializer"/>
|
||||
</int:chain>
|
||||
|
||||
<bean id="testSerializer" class="org.springframework.integration.redis.util.CustomJsonSerializer"/>
|
||||
|
||||
</beans>
|
||||
@@ -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<String, ?> 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<String, ?> redisTemplate2 = new RedisTemplate<String, Object>();
|
||||
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<String> message = MessageBuilder.withPayload("testing").build();
|
||||
handler.handleMessage(message);
|
||||
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
|
||||
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>(Object.class));
|
||||
|
||||
RedisTemplate<String, ?> redisTemplate = new StringRedisTemplate();
|
||||
redisTemplate.setConnectionFactory(connectionFactory);
|
||||
redisTemplate.setConnectionFactory(this.connectionFactory);
|
||||
redisTemplate.afterPropertiesSet();
|
||||
|
||||
handler.handleMessage(new GenericMessage<Object>(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<Object> message = new GenericMessage<Object>(queueName);
|
||||
this.sendChannel.send(message);
|
||||
|
||||
RedisTemplate<String, String> redisTemplate = new StringRedisTemplate();
|
||||
redisTemplate.setConnectionFactory(this.connectionFactory);
|
||||
redisTemplate.afterPropertiesSet();
|
||||
|
||||
String result = redisTemplate.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS);
|
||||
assertNotNull(result);
|
||||
InboundMessageMapper<String> mapper = new JsonInboundMessageMapper(String.class, new Jackson2JsonMessageParser());
|
||||
Message<?> resultMessage = mapper.toMessage(result);
|
||||
assertEquals(message.getPayload(), resultMessage.getPayload());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<UUID, Object> rt = new RedisTemplate<UUID, Object>();
|
||||
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();
|
||||
|
||||
@@ -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<Message<?>> {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private final InboundMessageMapper<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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:
|
||||
* <p>
|
||||
*
|
||||
* <pre>
|
||||
* ANY_HEADER_KEY = "foo";
|
||||
* ANY_HEADER_VALUE = "bar";
|
||||
* <pre class="code">
|
||||
* {@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)));
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* For multiple entries to match all:
|
||||
* <p>
|
||||
* <pre>
|
||||
* Map<String, Object> expectedInHeaderMap = new HashMap<String, Object>();
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* Map<String, Object> expectedInHeaderMap = new HashMap<String, Object>();
|
||||
* expectedInHeaderMap.put(ANY_HEADER_KEY, ANY_HEADER_VALUE);
|
||||
* expectedInHeaderMap.put(OTHER_HEADER_KEY, is(OTHER_HEADER_VALUE));
|
||||
* assertThat(message, HeaderMatcher.hasAllEntries(expectedInHeaderMap));
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* For a single key:
|
||||
* <p>
|
||||
*
|
||||
* <pre>
|
||||
* ANY_HEADER_KEY = "foo";
|
||||
* <pre class="code">
|
||||
* ANY_HEADER_KEY = "foo";
|
||||
* assertThat(message, HeaderMatcher.hasKey(ANY_HEADER_KEY));
|
||||
* </pre>
|
||||
*
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.hamcrest.core.IsEqual;
|
||||
* It is possible to match a single entry by value or matcher like this:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* <pre class="code">
|
||||
* 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:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* Map<String, Object> expectedInMap = new HashMap<String, Object>();
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* Map<String, Object> expectedInMap = new HashMap<String, Object>();
|
||||
* expectedInMap.put(SOME_KEY, SOME_VALUE);
|
||||
* expectedInMap.put(OTHER_KEY, is(OTHER_VALUE));
|
||||
* assertThat(map, hasAllEntries(expectedInMap));
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>If you only need to verify the existence of a key:</p>
|
||||
*
|
||||
* <pre>
|
||||
* <pre class="code">
|
||||
* assertThat(map, hasKey(SOME_KEY));
|
||||
* </pre>
|
||||
*
|
||||
|
||||
@@ -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)}:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* @Mock
|
||||
* MessageHandler handler;
|
||||
* ...
|
||||
* handler.handleMessage(message);
|
||||
* verify(handler).handleMessage(messageWithPayload(SOME_PAYLOAD));
|
||||
* verify(handler).handleMessage(messageWithPayload(is(SOME_CLASS)));
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* With {@link Mockito#when(Object)}:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* ...
|
||||
* when(channel.send(messageWithPayload(SOME_PAYLOAD))).thenReturn(true);
|
||||
* assertThat(channel.send(message), is(true));
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Alex Peters
|
||||
@@ -64,12 +69,12 @@ public class MockitoMessageMatchers {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Message<T> messageWithPayload(Matcher<T> payloadMatcher) {
|
||||
return (Message<T>) argThat(hasPayload(payloadMatcher));
|
||||
return argThat(hasPayload(payloadMatcher));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Message<T> messageWithPayload(T payload) {
|
||||
return (Message<T>) argThat(hasPayload(payload));
|
||||
return argThat(hasPayload(payload));
|
||||
}
|
||||
|
||||
public static Message<?> messageWithHeaderEntry(String key, Object value) {
|
||||
|
||||
@@ -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:
|
||||
* <p>
|
||||
* <pre>
|
||||
* ANY_PAYLOAD = new BigDecimal("1.123");
|
||||
* Message<BigDecimal message = MessageBuilder.withPayload(ANY_PAYLOAD).build();
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* ANY_PAYLOAD = new BigDecimal("1.123");
|
||||
* Message<BigDecimal message = MessageBuilder.withPayload(ANY_PAYLOAD).build();
|
||||
* assertThat(message, hasPayload(ANY_PAYLOjAD));
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* An example using {@link Assert#assertThat(Object, Matcher)} delegating to
|
||||
* another {@link Matcher}.
|
||||
* <p>
|
||||
* <pre>
|
||||
* ANY_PAYLOAD = new BigDecimal("1.123");
|
||||
* <pre class="code">
|
||||
* 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))))); *
|
||||
|
||||
@@ -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:
|
||||
*
|
||||
* <pre>
|
||||
* <pre class="code">
|
||||
* {@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:
|
||||
*
|
||||
* <pre>
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* <bean id="tcpIpUtils" class="org.springframework.integration.test.util.SocketUtils" />
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<T> 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<T> tweets = new LinkedBlockingQueue<T>();
|
||||
|
||||
private volatile int prefetchThreshold = 0;
|
||||
@@ -70,25 +77,36 @@ abstract class AbstractTwitterMessageSource<T> 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<T> 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<T> 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<T> 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<T> {
|
||||
|
||||
public int compare(T tweet1, T tweet2) {
|
||||
@@ -230,6 +244,7 @@ abstract class AbstractTwitterMessageSource<T> extends IntegrationObjectSupport
|
||||
throw new IllegalArgumentException("Uncomparable Twitter objects: " + tweet1 + " and " + tweet2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<DirectMessage> {
|
||||
|
||||
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
|
||||
|
||||
@@ -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<Tweet> {
|
||||
|
||||
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";
|
||||
|
||||
@@ -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<T
|
||||
private volatile String query;
|
||||
|
||||
|
||||
public SearchReceivingMessageSource(Twitter twitter) {
|
||||
super(twitter);
|
||||
public SearchReceivingMessageSource(Twitter twitter, String metadataKey) {
|
||||
super(twitter, metadataKey);
|
||||
}
|
||||
|
||||
|
||||
public void setQuery(String query) {
|
||||
Assert.hasText(query, "'query' must not be null");
|
||||
this.query = query;
|
||||
@@ -47,7 +46,7 @@ public class SearchReceivingMessageSource extends AbstractTwitterMessageSource<T
|
||||
|
||||
@Override
|
||||
public String getComponentType() {
|
||||
return "twitter:search-inbound-channel-adapter";
|
||||
return "twitter:search-inbound-channel-adapter";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.springframework.social.twitter.api.Tweet;
|
||||
import org.springframework.social.twitter.api.Twitter;
|
||||
|
||||
/**
|
||||
* This {@link org.springframework.integration.core.MessageSource} lets Spring Integration consume
|
||||
* This {@link org.springframework.integration.core.MessageSource} lets Spring Integration consume
|
||||
* given account's timeline as messages. It has support for dynamic throttling of API requests.
|
||||
*
|
||||
* @author Josh Long
|
||||
@@ -31,14 +31,13 @@ import org.springframework.social.twitter.api.Twitter;
|
||||
*/
|
||||
public class TimelineReceivingMessageSource extends AbstractTwitterMessageSource<Tweet> {
|
||||
|
||||
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
|
||||
|
||||
@@ -123,7 +123,28 @@
|
||||
<xsd:sequence>
|
||||
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="integration:channelAdapterAttributes"/>
|
||||
<xsd:attribute name="id" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
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.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.MessageChannel" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Identifies the channel the attached to this adapter, to which messages will be sent.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="integration:smartLifeCycleAttributeGroup"/>
|
||||
<xsd:attribute name="twitter-template" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
@@ -136,6 +157,21 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="metadata-store" use="optional" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
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.
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.metadata.MetadataStore" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="outbound-twitter-type">
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<DirectMessage> message = (Message<DirectMessage>) tSource.receive();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans
|
||||
xmlns="http://www.springframework.org/schema/beans"
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-twitter="http://www.springframework.org/schema/integration/twitter"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/integration/twitter http://www.springframework.org/schema/integration/twitter/spring-integration-twitter.xsd">
|
||||
|
||||
<int:channel id="inbound_twitter"/>
|
||||
<int:channel id="inbound_twitter">
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
|
||||
<int-twitter:search-inbound-channel-adapter id="twitterSearchAdapter"
|
||||
query="springintegration"
|
||||
twitter-template="twitterTemplate"
|
||||
channel="inbound_twitter"
|
||||
metadata-store="redisMetadataStore"
|
||||
auto-startup="false">
|
||||
<int:poller fixed-rate="5000" max-messages-per-poll="3"/>
|
||||
<int:poller fixed-rate="100" max-messages-per-poll="3"/>
|
||||
</int-twitter:search-inbound-channel-adapter>
|
||||
|
||||
<bean id="metadataStore" class="org.springframework.integration.redis.store.metadata.RedisMetadataStore">
|
||||
<bean id="redisMetadataStore" class="org.springframework.integration.redis.metadata.RedisMetadataStore">
|
||||
<constructor-arg name="connectionFactory" ref="redisConnectionFactory"/>
|
||||
</bean>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Tweet> message = (Message<Tweet>) tSource.receive();
|
||||
|
||||
@@ -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 <emphasis>published date</emphasis> field. Every time a new Message is generated and sent,
|
||||
Spring Integration will store the value of the latest <emphasis>published date</emphasis> in an instance of the
|
||||
<classname>org.springframework.integration.store.MetadataStore</classname> strategy. The MetadataStore interface is
|
||||
<classname>org.springframework.integration.metadata.MetadataStore</classname> 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.
|
||||
</para>
|
||||
<para>
|
||||
The default rule for locating this metadata store is as follows:
|
||||
<emphasis>Spring Integration</emphasis> will look for a bean of type
|
||||
<classname>org.springframework.integration.store.MetadataStore</classname> in
|
||||
<classname>org.springframework.integration.metadata.MetadataStore</classname> in
|
||||
the ApplicationContext. If one is found then it will be used, otherwise
|
||||
it will create a new instance of <classname>SimpleMetadataStore</classname>
|
||||
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
|
||||
<interfacename>MetadataStore</interfacename> interface (e.g. JdbcMetadataStore)
|
||||
and configure it as bean in the Application Context.
|
||||
</para>
|
||||
<note>
|
||||
The key used to persist the latest <emphasis>published date</emphasis> is the value of the (required)
|
||||
<code>id</code> attribute of the Feed Inbound Channel Adapter component plus the <code>feedUrl</code>
|
||||
from the adapter's configuration.
|
||||
</note>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
@@ -121,10 +121,11 @@
|
||||
<para>
|
||||
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 <code>UUID</code>. Beginning with Spring Integration 3.0, the default strategy
|
||||
used for id generation is to use the <code>com.eaio.uuid</code> package to
|
||||
generate Type 1 UUIDs. This is much more efficient than the previous
|
||||
<code>java.util.UUID.randomUUID()</code> implementation.
|
||||
a <code>UUID</code>. Beginning with <emphasis>Spring Integration 3.0</emphasis>,
|
||||
the default strategy used for id generation is more efficient than the previous
|
||||
<code>java.util.UUID.randomUUID()</code> implementation. It uses simple random
|
||||
numbers based on a secure random seed, instead of creating a secure random
|
||||
number each time.
|
||||
</para>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
<important>
|
||||
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.
|
||||
</important>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -152,6 +152,12 @@ rt.setConnectionFactory(redisConnectionFactory);]]></programlisting>
|
||||
|
||||
<para>Inbound adapters can subscribe to multiple topic names hence the comma-delimited set of values in the
|
||||
<code>topics</code> attribute.</para>
|
||||
<para>
|
||||
Inbound adapters can use a <classname>RedisSerializer</classname> to deserialize the body of Redis Messages.
|
||||
The <code>serializer</code> attribute of the <code><int-redis:inbound-channel-adapter></code> can be set to an
|
||||
empty string, which results in a <code>null</code> value for the <classname>RedisSerializer</classname> property.
|
||||
In this case the raw <code>byte[]</code> bodies of Redis Messages are provided as the message payloads.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="redis-outbound-channel-adapter">
|
||||
@@ -178,7 +184,166 @@ rt.setConnectionFactory(redisConnectionFactory);]]></programlisting>
|
||||
a <classname>RedisConnectionFactory</classname> which was defined with '<code>redisConnectionFactory</code>' as its bean name.
|
||||
This example also includes the optional, custom <classname>MessageConverter</classname> (the '<code>testConverter</code>' bean).
|
||||
</para>
|
||||
<para>
|
||||
Since <emphasis>Spring Integration 3.0</emphasis>, the <code><int-redis:outbound-channel-adapter></code>,
|
||||
as an alternative to the <code>topic</code> attribute, has the <code>topic-expression</code> attribute to determine
|
||||
the Redis topic against the Message at runtime. These attributes are mutually exclusive.
|
||||
</para>
|
||||
</section>
|
||||
<section id="redis-queue-inbound-channel-adapter">
|
||||
<title>Redis Queue Inbound Channel Adapter</title>
|
||||
<para>
|
||||
Since <emphasis>Spring Integration 3.0</emphasis>, 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.
|
||||
<programlisting language="xml"><![CDATA[<int-redis:queue-inbound-channel-adapter id="" ]]><co id="redis-m-d-c-a-id"/><![CDATA[
|
||||
channel="" ]]><co id="redis-m-d-c-a-channel"/><![CDATA[
|
||||
auto-startup="" ]]><co id="redis-m-d-c-a-autoStartup"/><![CDATA[
|
||||
phase="" ]]><co id="redis-m-d-c-a-phase"/><![CDATA[
|
||||
connection-factory="" ]]><co id="redis-m-d-c-a-connectionFactory"/><![CDATA[
|
||||
queue="" ]]><co id="redis-m-d-c-a-queue"/><![CDATA[
|
||||
error-channel="" ]]><co id="redis-m-d-c-a-errorChannel"/><![CDATA[
|
||||
serializer="" ]]><co id="redis-m-d-c-a-serializer"/><![CDATA[
|
||||
receive-timeout="" ]]><co id="redis-m-d-c-a-receiveTimeout"/><![CDATA[
|
||||
expect-message="" ]]><co id="redis-m-d-c-a-expectMessage"/><![CDATA[
|
||||
task-executor=""/> ]]><co id="redis-m-d-c-a-task-executor"/>
|
||||
</programlisting>
|
||||
<calloutlist>
|
||||
<callout arearefs="redis-m-d-c-a-id">
|
||||
<para>
|
||||
The component bean name. If the <code>channel</code> attribute isn't provided a <classname>DirectChannel</classname>
|
||||
is created and registered with application context with this <code>id</code> attribute as the bean name.
|
||||
In this case, the endpoint itself is registered with the bean name <code>id + '.adapter'</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-m-d-c-a-channel">
|
||||
<para>
|
||||
The <interfacename>MessageChannel</interfacename> to which to send <interfacename>Message</interfacename>s from this Endpoint.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-m-d-c-a-autoStartup">
|
||||
<para>
|
||||
A <interfacename>SmartLifecycle</interfacename> attribute to specify whether this Endpoint should start automatically after
|
||||
the application context start or not. Default is <code>true</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-m-d-c-a-phase">
|
||||
<para>
|
||||
A <interfacename>SmartLifecycle</interfacename> attribute to specify the <emphasis>phase</emphasis> in which
|
||||
this Endpoint will be started. Default is <code>0</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-m-d-c-a-connectionFactory">
|
||||
<para>
|
||||
A reference to a <interfacename>RedisConnectionFactory</interfacename> bean. Defaults to
|
||||
<code>redisConnectionFactory</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-m-d-c-a-queue">
|
||||
<para>
|
||||
The name of the Redis List on which the queue-based 'right pop' operation is performed to get Redis messages.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-m-d-c-a-errorChannel">
|
||||
<para>
|
||||
The <interfacename>MessageChannel</interfacename> to which to send <interfacename>ErrorMessage</interfacename>s with
|
||||
<interfacename>Exception</interfacename>s from the listening task of the Endpoint.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-m-d-c-a-serializer">
|
||||
<para>
|
||||
The <interfacename>RedisSerializer</interfacename> bean reference. Can be an empty string, which means 'no serializer'.
|
||||
In this case the raw <code>byte[]</code> from the inbound Redis message is sent to the <code>channel</code> as the
|
||||
<interfacename>Message</interfacename> payload. By default it is a <classname>JdkSerializationRedisSerializer</classname>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-m-d-c-a-receiveTimeout">
|
||||
<para>
|
||||
The timeout in milliseconds for 'right pop' operation to wait for a Redis message from the queue. Default is 1 second.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-m-d-c-a-expectMessage">
|
||||
<para>
|
||||
Specify if this Endpoint expects data from the Redis queue to contain entire <interfacename>Message</interfacename>s.
|
||||
If this attribute is set to <code>true</code>, the <code>serializer</code> can't be an empty string because messages
|
||||
require some form of deserialization (JDK serialization by default).
|
||||
Default is <code>false</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-m-d-c-a-task-executor">
|
||||
<para>
|
||||
A reference to a Spring <interfacename>TaskExecutor</interfacename> (or standard JDK 1.5+ <interfacename>Executor</interfacename>)
|
||||
bean. It is used for the underlying listening task. By default a <classname>SimpleAsyncTaskExecutor</classname>
|
||||
is used.
|
||||
</para>
|
||||
</callout>
|
||||
</calloutlist>
|
||||
</para>
|
||||
</section>
|
||||
<section id="redis-queue-outbound-channel-adapter">
|
||||
<title>Redis Queue Outbound Channel Adapter</title>
|
||||
<para>
|
||||
Since <emphasis>Spring Integration 3.0</emphasis>, a Queue Outbound Channel Adapter
|
||||
is available to 'left push' to a Redis List from Spring Integration messages:
|
||||
<programlisting language="xml"><![CDATA[<int-redis:queue-outbound-channel-adapter id="" ]]><co id="redis-q-u-c-a-id"/><![CDATA[
|
||||
channel="" ]]><co id="redis-q-u-c-a-channel"/><![CDATA[
|
||||
connection-factory="" ]]><co id="redis-q-u-c-a-connectionFactory"/><![CDATA[
|
||||
queue="" ]]><co id="redis-q-u-c-a-queue"/><![CDATA[
|
||||
queue-expression="" ]]><co id="redis-q-u-c-a-queueExpression"/><![CDATA[
|
||||
serializer="" ]]><co id="redis-q-u-c-a-serializer"/><![CDATA[
|
||||
extract-payload="" />]]><co id="redis-q-u-c-a-extractPayload"/>
|
||||
</programlisting>
|
||||
<calloutlist>
|
||||
<callout arearefs="redis-q-u-c-a-id">
|
||||
<para>
|
||||
The component bean name. If the <code>channel</code> attribute isn't provided, a <classname>DirectChannel</classname>
|
||||
is created and registered with the application context with this <code>id</code> attribute as the bean name.
|
||||
In this case, the endpoint is registered with the bean name <code>id + '.adapter'</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-q-u-c-a-channel">
|
||||
<para>
|
||||
The <interfacename>MessageChannel</interfacename> from which this Endpoint receives <interfacename>Message</interfacename>s.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-q-u-c-a-connectionFactory">
|
||||
<para>
|
||||
A reference to a <interfacename>RedisConnectionFactory</interfacename> bean. Defaults to
|
||||
<code>redisConnectionFactory</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-q-u-c-a-queue">
|
||||
<para>
|
||||
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 <code>queue-expression</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-q-u-c-a-queueExpression">
|
||||
<para>
|
||||
A SpEL <interfacename>Expression</interfacename> to determine the name of the Redis List
|
||||
using the incoming <interfacename>Message</interfacename> at runtime as the <code>#root</code> variable.
|
||||
This attribute is mutually exclusive with <code>queue</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-m-d-c-a-serializer">
|
||||
<para>
|
||||
A <interfacename>RedisSerializer</interfacename> bean reference.
|
||||
By default it is a <classname>JdkSerializationRedisSerializer</classname>.
|
||||
However, for <classname>String</classname> payloads, a <classname>StringRedisSerializer</classname>
|
||||
is used, if a <code>serializer</code> reference isn't provided.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="redis-q-u-c-a-extractPayload">
|
||||
<para>
|
||||
Specify if this Endpoint should send just the <emphasis>payload</emphasis> to the Redis queue,
|
||||
or the entire <interfacename>Message</interfacename>.
|
||||
Default is <code>true
|
||||
</code>.
|
||||
</para>
|
||||
</callout>
|
||||
</calloutlist>
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="redis-message-store">
|
||||
@@ -423,4 +588,4 @@ the serialization of values, you may want to consider providing your own
|
||||
</para>
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
</chapter>
|
||||
|
||||
@@ -184,10 +184,33 @@ public class Kid {
|
||||
// setters and getters are omitted
|
||||
}]]></programlisting>
|
||||
</para>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
For example:
|
||||
<programlisting language="java"><![CDATA[public class Parent {
|
||||
private Child child;
|
||||
private String name;
|
||||
// setters and getters are omitted
|
||||
}
|
||||
|
||||
public class Child {
|
||||
private String name;
|
||||
private List<String> nickNames;
|
||||
// setters and getters are omitted
|
||||
}]]></programlisting>
|
||||
... will be transformed to a Map which looks like this:
|
||||
<code>{name=George, child={name=Jenna, nickNames=[Bimbo, ...]}}</code>
|
||||
</para>
|
||||
<para>
|
||||
To configure these transformers, Spring Integration provides namespace support
|
||||
Object-to-Map:
|
||||
<programlisting language="xml"><![CDATA[<int:object-to-map-transformer input-channel="directInput" output-channel="output"/>]]></programlisting>
|
||||
or
|
||||
<programlisting language="xml"><![CDATA[<int:object-to-map-transformer input-channel="directInput" output-channel="output" flatten="false"/>]]></programlisting>
|
||||
Map-to-Object
|
||||
<programlisting language="xml"><![CDATA[<int:map-to-object-transformer input-channel="input"
|
||||
output-channel="output"
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
subscribers who are known as followers.
|
||||
</para>
|
||||
<para>
|
||||
<important>
|
||||
Previous versions of Spring Integration were dependent upon the <link linkend="http://twitter4j.org/en/index.html">Twitter4J API</link>,
|
||||
but with the release of <link linkend="http://www.springsource.org/spring-social">Spring Social 1.0 GA</link>,
|
||||
Spring Integration, as of version 2.1, now builds directly upon Spring Social's Twitter support, instead of Twitter4J.
|
||||
</important>
|
||||
<important>
|
||||
Versions of Spring Integration prior to 2.1 were dependent upon the <ulink url="http://twitter4j.org">Twitter4J API</ulink>,
|
||||
but with the release of <ulink url="http://projects.spring.io/spring-social">Spring Social 1.0 GA</ulink>,
|
||||
Spring Integration, as of version 2.1, now builds directly upon Spring Social's Twitter support, instead of Twitter4J.
|
||||
</important>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@@ -39,9 +39,9 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/twitter
|
||||
<para>
|
||||
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 <link linkend="http://oauth.net/">http://oauth.net/</link> or
|
||||
in this article <link linkend="http://hueniverse.com/oauth/">http://hueniverse.com/oauth/</link> from Hueniverse.
|
||||
Please also see <link linkend="http://dev.twitter.com/pages/oauth_faq">OAuth FAQ</link> for more information about OAuth and Twitter.
|
||||
sharing their password. More information can be found at <ulink url="http://oauth.net">http://oauth.net</ulink> or
|
||||
in this article <ulink url="http://hueniverse.com/oauth">http://hueniverse.com/oauth</ulink> from Hueniverse.
|
||||
Please also see <ulink url="http://dev.twitter.com/pages/oauth_faq">OAuth FAQ</ulink> for more information about OAuth and Twitter.
|
||||
</para>
|
||||
<para>
|
||||
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
|
||||
<para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>Go to <link linkend="http://dev.twitter.com/">http://dev.twitter.com/</link></para>
|
||||
<para>Go to <ulink url="http://dev.twitter.com">http://dev.twitter.com</ulink></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Click on the <code>Register an app</code> link and fill out all required fields on the form provided;
|
||||
@@ -121,31 +121,39 @@ twitter.oauth.accessTokenSecret=AbRxUAvyNCtqQtxFK8w5ZMtMj20KFhB6o]]></programlis
|
||||
<title>Twitter Inbound Adapters</title>
|
||||
<para>
|
||||
Twitter inbound adapters allow you to receive Twitter Messages. There are several types of
|
||||
<link linkend="http://support.twitter.com/groups/31-twitter-basics/topics/109-tweets-messages/articles/119138-types-of-tweets-and-where-they-appear">twitter messages, or tweets</link>
|
||||
<ulink url="http://support.twitter.com/articles/119138-types-of-tweets-and-where-they-appear">twitter messages, or tweets</ulink>
|
||||
</para>
|
||||
<para>
|
||||
<emphasis>Spring Integration version 2.0 and above</emphasis> provides support for receiving tweets as <emphasis>Timeline Updates</emphasis>,
|
||||
<emphasis>Direct Messages</emphasis>, <emphasis>Mention Messages</emphasis> as well as Search Results.
|
||||
</para>
|
||||
<para>
|
||||
Every Inbound Twitter Channel Adapter is a <emphasis>Polling Consumer</emphasis> 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: <link linkend="http://dev.twitter.com/pages/rate-limiting">Rate Limiting</link>. 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.
|
||||
</para>
|
||||
<para>
|
||||
<important>
|
||||
<para>
|
||||
Every Inbound Twitter Channel Adapter is a <emphasis>Polling Consumer</emphasis> which means you have to provide a poller
|
||||
configuration.
|
||||
Twitter defines a concept of Rate Limiting. You can read more
|
||||
about it here: <ulink url="https://dev.twitter.com/docs/rate-limiting/1.1">Rate Limiting</ulink>. 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.
|
||||
</para>
|
||||
<para>
|
||||
With Spring Integration prior to <emphasis>version 3.0</emphasis>, 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.
|
||||
</para>
|
||||
</important>
|
||||
<para>
|
||||
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 <classname>org.springframework.integration.store.MetadataStore</classname> which is a
|
||||
The latest Tweet id will be stored in an instance of the <classname>org.springframework.integration.metadata.MetadataStore</classname> 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
|
||||
<classname>org.springframework.integration.store.MetadataStore</classname> in the ApplicationContext.
|
||||
If one is found then it will be used, otherwise it will create a new instance of <classname>SimpleMetadataStore</classname>
|
||||
<classname>org.springframework.integration.metadata.MetadataStore</classname> in the ApplicationContext. Alternatively,
|
||||
you can configure an explicit <classname>MetadataStore</classname> on the adapter.
|
||||
If there is no explicit or default store, the adapter will create a new instance of <classname>SimpleMetadataStore</classname>
|
||||
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 <classname>PropertiesPersistingMetadataStore</classname> (which is backed by a properties file, and a persister
|
||||
@@ -165,8 +173,16 @@ twitter.oauth.accessTokenSecret=AbRxUAvyNCtqQtxFK8w5ZMtMj20KFhB6o]]></programlis
|
||||
</warning>
|
||||
<programlisting language="xml"><![CDATA[<bean id="metadataStore" class="o.s.i.store.PropertiesPersistingMetadataStore"/>
|
||||
]]></programlisting>
|
||||
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.
|
||||
<para>
|
||||
If the <classname>MetadataStore</classname> is persistent, during initialization, any Inbound Twitter Adapter (see below)
|
||||
will retrieve the latest tweet id that has already been sent by the adapter.
|
||||
</para>
|
||||
<note>
|
||||
The key used to persist the latest <emphasis>twitter id</emphasis> is the value of the (required)
|
||||
<code>id</code> attribute of the Twitter Inbound Channel Adapter component plus the <code>profileId</code>
|
||||
of the Twitter user.
|
||||
</note>
|
||||
|
||||
<section id="inbound-twitter-update">
|
||||
<title>Inbound Message Channel Adapter</title>
|
||||
<para>
|
||||
|
||||
@@ -143,12 +143,12 @@
|
||||
For more information see <xref linkend="http-namespace"/>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="3.0-redis-meta-data-store">
|
||||
<title>Redis Metadata Store</title>
|
||||
<section id="3.0-redis-new-components">
|
||||
<title>Redis: New Components</title>
|
||||
<para>
|
||||
A new Redis-based
|
||||
<interfacename><ulink url="http://docs.spring.io/spring-integration/docs/latest-ga/api/org/springframework/integration/store/MetadataStore.html">MetadataStore</ulink></interfacename>
|
||||
implementation was added. The <classname>RedisMetadataStore</classname> can
|
||||
implementation has been added. The <classname>RedisMetadataStore</classname> can
|
||||
be used to maintain state of a <interfacename>MetadataStore</interfacename>
|
||||
across application restarts. This new <interfacename>MetadataStore</interfacename>
|
||||
implementation can be used with adapters such as:
|
||||
@@ -158,7 +158,12 @@
|
||||
<listitem>Feed Inbound Channel Adapter</listitem>
|
||||
</itemizedlist>
|
||||
<para>
|
||||
For more information see <xref linkend="redis-metadata-store" />.
|
||||
New queue-based components have been added. The <code><int-redis:queue-inbound-channel-adapter/></code>
|
||||
and the <code><int-redis:queue-outbound-channel-adapter/></code> components are provided
|
||||
to perform 'right pop' and 'left push' operations on a Redis List, respectively.
|
||||
</para>
|
||||
<para>
|
||||
For more information see <xref linkend="redis" />.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
@@ -462,8 +467,8 @@
|
||||
<title>Message ID Generation</title>
|
||||
<para>
|
||||
Previously, message ids were generated using the JDK <code>UUID.randomUUID()</code> method. With this
|
||||
release, the default mechanism has been changed to use the <code>com.eaio.uuid</code> 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 <xref linkend="message-id-generation"/>.
|
||||
</para>
|
||||
@@ -567,5 +572,23 @@
|
||||
result set respectively. For more information see <xref linkend="jpa"/>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="3.0-redis">
|
||||
<title>Redis Adapters Changers</title>
|
||||
<para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
The Redis Inbound Channel Adapter can now use a <code>null</code> value for <code>serializer</code>
|
||||
property, with the raw data being the message payload.
|
||||
</listitem>
|
||||
<listitem>
|
||||
The Redis Outbound Channel Adapter now has the <code>topic-expression</code> property to determine
|
||||
the Redis topic against the Message at runtime.
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
For more information, see <xref linkend="redis"/>.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
Reference in New Issue
Block a user