DATAKV-25

+ add namespace for Redis pubsub
This commit is contained in:
Costin Leau
2011-01-24 21:52:51 +02:00
parent 61afbd2e49
commit 31a2a2395d
10 changed files with 427 additions and 2 deletions

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2011 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.data.keyvalue.redis.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.keyvalue.redis.listener.ChannelTopic;
import org.springframework.data.keyvalue.redis.listener.PatternTopic;
import org.springframework.data.keyvalue.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.keyvalue.redis.listener.Topic;
import org.springframework.data.keyvalue.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
/**
* Parser for the JMS <code>&lt;listener-container&gt;</code> element.
*
* @author Costin Leau
*/
class RedisListenerContainerParser extends AbstractSimpleBeanDefinitionParser {
@Override
protected Class<RedisMessageListenerContainer> getBeanClass(Element element) {
return RedisMessageListenerContainer.class;
}
@SuppressWarnings("unchecked")
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
// parse attributes (but replace the value assignment with references)
NamedNodeMap attributes = element.getAttributes();
for (int x = 0; x < attributes.getLength(); x++) {
Attr attribute = (Attr) attributes.item(x);
if (isEligibleAttribute(attribute, parserContext)) {
String propertyName = extractPropertyName(attribute.getLocalName());
Assert.state(StringUtils.hasText(propertyName),
"Illegal property name returned from 'extractPropertyName(String)': cannot be null or empty.");
builder.addPropertyReference(propertyName, attribute.getValue());
}
}
postProcess(builder, element);
// parse nested listeners
List<Element> listDefs = DomUtils.getChildElementsByTagName(element, "listener");
if (!listDefs.isEmpty()) {
ManagedMap<BeanDefinition, Collection<? extends BeanDefinition>> listeners = new ManagedMap<BeanDefinition, Collection<? extends BeanDefinition>>(
listDefs.size());
for (Element listElement : listDefs) {
Object[] listenerDefinition = parseListener(listElement);
listeners.put((BeanDefinition) listenerDefinition[0],
(Collection<? extends BeanDefinition>) listenerDefinition[1]);
}
builder.addPropertyValue("messageListeners", listeners);
}
}
/**
* Parses a listener definition. Returns the listener bean reference definition (as the array first entry) and its associated topics (also as bean definitions).
*
* @param element
* @return
*/
private Object[] parseListener(Element element) {
Object[] ret = new Object[2];
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MessageListenerAdapter.class);
builder.addConstructorArgReference(element.getAttribute("ref"));
String method = element.getAttribute("method");
if (StringUtils.hasText(method)){
builder.addPropertyValue("defaultListenerMethod", method);
}
String serializer = element.getAttribute("serializer");
if (StringUtils.hasText(serializer)){
builder.addPropertyReference("serializer", serializer);
}
// assemble topics
Collection<Topic> topics = new ArrayList<Topic>();
// get channels
String channels = element.getAttribute("channel");
if (StringUtils.hasText(channels)) {
String[] array = StringUtils.delimitedListToStringArray(channels, " ");
for (String string : array) {
topics.add(new ChannelTopic(string));
}
}
// get patterns
String patterns = element.getAttribute("pattern");
if (StringUtils.hasText(patterns)) {
String[] array = StringUtils.delimitedListToStringArray(patterns, " ");
for (String string : array) {
topics.add(new PatternTopic(string));
}
}
ret[0] = builder.getBeanDefinition();
ret[1] = topics;
return ret;
}
@Override
protected boolean shouldGenerateId() {
return true;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2011 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.data.keyvalue.redis.config;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* {@link NamespaceHandler} for Spring Data Redis namespace.
*
* @author Costin Leau
*/
class RedisNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("listener-container", new RedisListenerContainerParser());
}
}

View File

@@ -0,0 +1 @@
http\://www.springframework.org/schema/redis=org.springframework.data.keyvalue.redis.config.RedisNamespaceHandler

View File

@@ -0,0 +1,2 @@
http\://www.springframework.org/schema/redis/spring-redis-1.0.xsd=org/springframework/data/keyvalue/redis/config/spring-redis-1.0.xsd
http\://www.springframework.org/schema/redis/spring-redis.xsd=org/springframework/data/keyvalue/redis/config/spring-redis-1.0.xsd

View File

@@ -0,0 +1,4 @@
# Tooling related information for the jms namespace
http\://www.springframework.org/schema/redis@name=redis Namespace
http\://www.springframework.org/schema/redis@prefix=redis
http\://www.springframework.org/schema/redis@icon=org/springframework/data/keyvalue/redis/config/spring-redis.gif

View File

@@ -0,0 +1,165 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.org/schema/redis"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://www.springframework.org/schema/redis"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool.xsd"/>
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the configuration elements for the Spring Data Redis support.
Allows for configuring Redis listener containers in XML 'shortcut' style.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="listener-container">
<xsd:annotation>
<xsd:documentation><![CDATA[
Container of Redis listeners. All listeners will be hosted by the same container.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.data.keyvalue.redis.listener.RedisMessageListenerContainer"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="listener" type="listenerType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="connection-factory" type="xsd:string" default="redisConnectionFactory">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the Redis ConnectionFactory bean.
Default is "redisConnectionFactory".
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.data.keyvalue.redis.connection.ConnectionFactory"/>
</tool:annotation>
</xsd:appinfo>
</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
Redis listener invokers. 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:attribute name="subscription-task-executor" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to a Spring TaskExecutor (or standard JDK 1.5 Executor) for listening
to Redis messages. By default reuses the 'task-executor' value.
]]></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:attribute name="topic-serializer" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the RedisSerializer strategy for converting Redis channels/patterns to
serialized format. Default is a StringRedisSerializer.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.data.keyvalue.redis.serializer.RedisSerializer"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-handler" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to an ErrorHandler strategy for handling any uncaught Exceptions
that may occur during the execution of the MessageListener.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.util.ErrorHandler"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="phase" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The lifecycle phase within which this container should start and stop. The lower
the value the earlier this container will start and the later it will stop. The
default is Integer.MAX_VALUE meaning the container will start as late as possible
and stop as soon as possible.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="listenerType">
<xsd:attribute name="ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The bean name of the listener object, implementing
the MessageListener interface or defining the specified listener method.
Required.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref"/>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="channel" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The channel(s) to which the listener is subscribed. Multiple values can be specified
by separating them with spaces.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="pattern" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The pattern(s) matching the channels to which the listener is subscribed. Multiple values can be specified
by separating them with spaces.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="method" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the listener method to invoke. If not specified,
the target bean is supposed to implement the MessageListener
interface or provide a method named 'handleMessage'.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="serializer" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the RedisSerializer strategy for converting Redis Messages to
listener method arguments. Default is a StringRedisSerializer.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.data.keyvalue.redis.serializer.RedisSerializer"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:schema>

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2011 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.data.keyvalue.redis.config;
import static org.junit.Assert.*;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.data.keyvalue.redis.listener.RedisMessageListenerContainer;
/**
* @author Costin Leau
*/
public class NamespaceTest {
private GenericXmlApplicationContext ctx;
@Before
public void setUp() {
ctx = new GenericXmlApplicationContext("/org/springframework/data/keyvalue/redis/config/namespace.xml");
}
@After
public void tearDown() {
if (ctx != null)
ctx.destroy();
}
@Test
public void testSanityTest() throws Exception {
RedisMessageListenerContainer container = ctx.getBean(RedisMessageListenerContainer.class);
assertTrue(container.isRunning());
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
}
}

View File

@@ -20,7 +20,11 @@ package org.springframework.data.keyvalue.redis.listener.adapter;
*/
public class RedisMDP {
public void handle(String message) {
public void handleMessage(String message) {
System.out.println("Received message " + message);
}
}
public void anotherHandle(String message) {
System.out.println("[*] Received message " + message);
}
}

View File

@@ -0,0 +1,26 @@
<?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:redis="http://www.springframework.org/schema/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/task http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/redis http://www.springframework.org/schema/redis/spring-redis.xsd">
<!-- the default ConnectionFactory -->
<bean id="redisConnectionFactory" class="org.springframework.data.keyvalue.redis.connection.jedis.JedisConnectionFactory"/>
<task:executor id="testTaskExecutor" />
<redis:listener-container task-executor="testTaskExecutor">
<!-- default handle method -->
<redis:listener ref="testBean1" channel="z1 z2" pattern="x*"/>
<!-- channel subscription only -->
<redis:listener ref="testBean1" method="anotherHandle" channel="x1" serializer="serializer"/>
</redis:listener-container>
<bean id="testBean1" class="org.springframework.data.keyvalue.redis.listener.adapter.RedisMDP"/>
<bean id="serializer" class="org.springframework.data.keyvalue.redis.serializer.StringRedisSerializer"/>
</beans>