DATAKV-58

+ add FactoryBean for creating collections on top of Redis keys
+ add dedicated namespace
+ code + integration tests
This commit is contained in:
Costin Leau
2011-04-15 20:44:58 +03:00
parent 30d82a3ae8
commit ffb61645de
10 changed files with 466 additions and 3 deletions

View File

@@ -0,0 +1,48 @@
/*
* 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.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.data.keyvalue.redis.support.collections.RedisCollectionFactoryBean;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Parser for the Redis <code>&lt;collection&gt;</code> element.
*
* @author Costin Leau
*/
public class RedisCollectionParser extends AbstractSimpleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return RedisCollectionFactoryBean.class;
}
@Override
protected void postProcess(BeanDefinitionBuilder beanDefinition, Element element) {
String template = element.getAttribute("template");
if (StringUtils.hasText(template)) {
beanDefinition.addPropertyReference("template", template);
}
}
@Override
protected boolean isEligibleAttribute(String attributeName) {
return super.isEligibleAttribute(attributeName) && (!"template".equals(attributeName));
}
}

View File

@@ -37,7 +37,7 @@ import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
/**
* Parser for the JMS <code>&lt;listener-container&gt;</code> element.
* Parser for the Redis <code>&lt;listener-container&gt;</code> element.
*
* @author Costin Leau
*/

View File

@@ -28,5 +28,6 @@ class RedisNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("listener-container", new RedisListenerContainerParser());
registerBeanDefinitionParser("collection", new RedisCollectionParser());
}
}

View File

@@ -0,0 +1,167 @@
/*
* 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.support.collections;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.keyvalue.redis.connection.DataType;
import org.springframework.data.keyvalue.redis.core.RedisTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Factory bean that facilitates creation of Redis-based collections. Supports list, set, zset (or sortedSet), map (or hash) and properties.
* Will use the key type if it exists or to create a dedicated collection (Properties vs Map).
* Otherwise uses the provided type (default is list).
*
* @author Costin Leau
*/
public class RedisCollectionFactoryBean implements InitializingBean, BeanNameAware, FactoryBean<RedisStore> {
public enum CollectionType {
LIST {
@Override
public DataType dataType() {
return DataType.LIST;
}
},
SET {
@Override
public DataType dataType() {
return DataType.SET;
}
},
ZSET {
@Override
public DataType dataType() {
return DataType.ZSET;
}
},
MAP {
@Override
public DataType dataType() {
return DataType.HASH;
}
},
PROPERTIES {
@Override
public DataType dataType() {
return DataType.HASH;
}
};
abstract DataType dataType();
}
private RedisStore store;
private CollectionType type = null;
private RedisTemplate<String, ?> template;
private String key;
private String beanName;
@Override
public void afterPropertiesSet() {
if (!StringUtils.hasText(key)) {
key = beanName;
}
Assert.hasText(key, "Collection key is required - no key or bean name specified");
Assert.notNull(template, "Redis template is required");
DataType dt = template.type(key);
// can't create store
Assert.isTrue(!DataType.STRING.equals(dt), "Cannot create store on keys of type 'string'");
store = createStore(dt);
if (store == null) {
if (type == null) {
type = CollectionType.LIST;
}
store = createStore(type.dataType());
}
}
private RedisStore createStore(DataType dt) {
switch (dt) {
case LIST:
return new DefaultRedisList(key, template);
case SET:
return new DefaultRedisSet(key, template);
case ZSET:
return new DefaultRedisZSet(key, template);
case HASH:
if (CollectionType.PROPERTIES.equals(type)) {
return new RedisProperties(key, template);
}
return new DefaultRedisMap(key, template);
}
return null;
}
@Override
public RedisStore getObject() {
return store;
}
@Override
public Class<?> getObjectType() {
return (store != null ? store.getClass() : RedisStore.class);
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
/**
* Sets the store type. Used if the key does not exist.
*
* @param type The type to set.
*/
public void setType(CollectionType type) {
this.type = type;
}
/**
* Sets the template used by the resulting store.
*
* @param template The template to set.
*/
public void setTemplate(RedisTemplate<String, ?> template) {
this.template = template;
}
/**
* Sets the key of the store.
*
* @param key The key to set.
*/
public void setKey(String key) {
this.key = key;
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.keyvalue.redis.support.collections;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -262,4 +264,14 @@ public class RedisProperties extends Properties implements RedisMap<Object, Obje
public Object replace(Object key, Object value) {
throw new UnsupportedOperationException();
}
@Override
public synchronized void storeToXML(OutputStream os, String comment, String encoding) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public synchronized void storeToXML(OutputStream os, String comment) throws IOException {
throw new UnsupportedOperationException();
}
}

View File

@@ -149,4 +149,61 @@ listener method arguments. Default is a StringRedisSerializer.
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="collection">
<xsd:annotation>
<xsd:documentation><![CDATA[
Factory creating collections on top of Redis keys.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.data.keyvalue.redis.support.collections.RedisCollectionFactoryBean"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="id" type="xsd:ID">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the Redis collection.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="key" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Redis key of the created collection. Defaults to bean id.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="template" type="xsd:string" default="redisTemplate">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to a RedisTemplate bean.Default is "redisTemplate".
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.data.keyvalue.redis.core.RedisTemplate"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="type" default="LIST" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The collection type (default is list).
If the key exists, its type takes priority. The type is used to disambiguate the collection type (map vs properties) or
specify one in case the key is missing.]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="LIST"/>
<xsd:enumeration value="SET"/>
<xsd:enumeration value="ZSET"/>
<xsd:enumeration value="MAP"/>
<xsd:enumeration value="PROPERTIES"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -0,0 +1,123 @@
/*
* 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.support.collections;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Test;
import org.springframework.data.keyvalue.redis.ConnectionFactoryTracker;
import org.springframework.data.keyvalue.redis.SettingsUtils;
import org.springframework.data.keyvalue.redis.connection.RedisConnection;
import org.springframework.data.keyvalue.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.keyvalue.redis.core.RedisCallback;
import org.springframework.data.keyvalue.redis.core.StringRedisTemplate;
import org.springframework.data.keyvalue.redis.support.collections.RedisCollectionFactoryBean.CollectionType;
/**
* @author Costin Leau
*/
public class RedisCollectionFactoryBeanTests {
protected ObjectFactory<String> factory = new StringObjectFactory();
protected StringRedisTemplate template;
protected RedisStore col;
public RedisCollectionFactoryBeanTests() {
JedisConnectionFactory jedisConnFactory = new JedisConnectionFactory();
jedisConnFactory.setUsePool(true);
jedisConnFactory.setPort(SettingsUtils.getPort());
jedisConnFactory.setHostName(SettingsUtils.getHost());
jedisConnFactory.afterPropertiesSet();
this.template = new StringRedisTemplate(jedisConnFactory);
ConnectionFactoryTracker.add(jedisConnFactory);
}
@AfterClass
public static void cleanUp() {
ConnectionFactoryTracker.cleanUp();
}
@After
public void tearDown() throws Exception {
// clean up the whole db
template.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) {
connection.flushDb();
return null;
}
});
}
private RedisStore createCollection(String key) {
return createCollection(key, null);
}
private RedisStore createCollection(String key, CollectionType type) {
RedisCollectionFactoryBean fb = new RedisCollectionFactoryBean();
fb.setKey(key);
fb.setTemplate(template);
fb.setType(type);
fb.afterPropertiesSet();
return fb.getObject();
}
@Test
public void testNone() throws Exception {
RedisStore store = createCollection("nosrt", CollectionType.PROPERTIES);
assertThat(store, instanceOf(RedisProperties.class));
store = createCollection("nosrt", CollectionType.MAP);
assertThat(store, instanceOf(DefaultRedisMap.class));
store = createCollection("nosrt", CollectionType.SET);
assertThat(store, instanceOf(DefaultRedisSet.class));
store = createCollection("nosrt", CollectionType.LIST);
assertThat(store, instanceOf(DefaultRedisList.class));
store = createCollection("nosrt");
assertThat(store, instanceOf(DefaultRedisList.class));
}
@Test
public void testExistingCol() throws Exception {
String key = "set";
String val = "value";
template.boundSetOps(key).add(val);
RedisStore col = createCollection(key);
assertThat(col, is(DefaultRedisSet.class));
key = "map";
template.boundHashOps(key).put(val, val);
col = createCollection(key);
assertThat(col, is(DefaultRedisMap.class));
col = createCollection(key, CollectionType.PROPERTIES);
assertThat(col, is(RedisProperties.class));
}
}

View File

@@ -19,6 +19,7 @@ import static org.junit.Assert.*;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collection;
@@ -128,7 +129,7 @@ public class RedisPropertiesTests extends RedisMapTests {
StringWriter writer = new StringWriter();
props.store(writer, "no-comment");
System.out.println(writer.toString());
//System.out.println(writer.toString());
}
@Test
@@ -165,7 +166,8 @@ public class RedisPropertiesTests extends RedisMapTests {
public void testPropertiesList() throws Exception {
defaults.setProperty("a", "b");
props.setProperty("x", "y");
props.list(System.out);
StringWriter wr = new StringWriter();
props.list(new PrintWriter(wr));
}
@Test

View File

@@ -0,0 +1,37 @@
/*
* 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.support.collections;
import java.util.Map;
import org.junit.Test;
import org.springframework.context.support.GenericXmlApplicationContext;
/**
* @author Costin Leau
*/
public class SupportXmlTests {
@Test
public void testContainerSetup() throws Exception {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(
"/org/springframework/data/keyvalue/redis/support/collections/container.xml");
RedisList list = ctx.getBean("non-existing", RedisList.class);
RedisProperties props = ctx.getBean("props", RedisProperties.class);
Map map = ctx.getBean("map", Map.class);
}
}

View File

@@ -0,0 +1,16 @@
<?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:p="http://www.springframework.org/schema/p"
xmlns:redis="http://www.springframework.org/schema/redis"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/redis http://www.springframework.org/schema/redis/spring-redis.xsd">
<bean id="connectionFactory" class="org.springframework.data.keyvalue.redis.connection.jedis.JedisConnectionFactory"/>
<bean id="redisTemplate" class="org.springframework.data.keyvalue.redis.core.StringRedisTemplate" p:connectionFactory-ref="connectionFactory"/>
<redis:collection id="non-existing" />
<redis:collection id="props" key="prop-key" type="PROPERTIES"/>
<redis:collection id="map" key="map-key" type="MAP"/>
</beans>