From ffb61645de6286b293eaeedead8c7688efbd3848 Mon Sep 17 00:00:00 2001 From: Costin Leau Date: Fri, 15 Apr 2011 20:44:58 +0300 Subject: [PATCH] DATAKV-58 + add FactoryBean for creating collections on top of Redis keys + add dedicated namespace + code + integration tests --- .../redis/config/RedisCollectionParser.java | 48 +++++ .../config/RedisListenerContainerParser.java | 2 +- .../redis/config/RedisNamespaceHandler.java | 1 + .../RedisCollectionFactoryBean.java | 167 ++++++++++++++++++ .../support/collections/RedisProperties.java | 12 ++ .../redis/config/spring-redis-1.0.xsd | 57 ++++++ .../RedisCollectionFactoryBeanTests.java | 123 +++++++++++++ .../collections/RedisPropertiesTests.java | 6 +- .../support/collections/SupportXmlTests.java | 37 ++++ .../redis/support/collections/container.xml | 16 ++ 10 files changed, 466 insertions(+), 3 deletions(-) create mode 100644 spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisCollectionParser.java create mode 100644 spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBean.java create mode 100644 spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBeanTests.java create mode 100644 spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/SupportXmlTests.java create mode 100644 spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/container.xml diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisCollectionParser.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisCollectionParser.java new file mode 100644 index 000000000..c806f8dcb --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisCollectionParser.java @@ -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 <collection> 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)); + } +} diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisListenerContainerParser.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisListenerContainerParser.java index 1c300a8f4..12fd192fd 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisListenerContainerParser.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisListenerContainerParser.java @@ -37,7 +37,7 @@ import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; /** - * Parser for the JMS <listener-container> element. + * Parser for the Redis <listener-container> element. * * @author Costin Leau */ diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisNamespaceHandler.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisNamespaceHandler.java index c2cc323e7..2a136f377 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisNamespaceHandler.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/config/RedisNamespaceHandler.java @@ -28,5 +28,6 @@ class RedisNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("listener-container", new RedisListenerContainerParser()); + registerBeanDefinitionParser("collection", new RedisCollectionParser()); } } diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBean.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBean.java new file mode 100644 index 000000000..0f0fa8249 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBean.java @@ -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 { + + 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 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 template) { + this.template = template; + } + + /** + * Sets the key of the store. + * + * @param key The key to set. + */ + public void setKey(String key) { + this.key = key; + } +} \ No newline at end of file diff --git a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisProperties.java b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisProperties.java index 22b44ddc3..0fec1d1b9 100644 --- a/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisProperties.java +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisProperties.java @@ -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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBeanTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBeanTests.java new file mode 100644 index 000000000..3f6742d40 --- /dev/null +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisCollectionFactoryBeanTests.java @@ -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 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() { + + @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)); + + } +} \ No newline at end of file diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisPropertiesTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisPropertiesTests.java index 0a9770b16..8053fde02 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisPropertiesTests.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisPropertiesTests.java @@ -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 diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/SupportXmlTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/SupportXmlTests.java new file mode 100644 index 000000000..026074fbd --- /dev/null +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/SupportXmlTests.java @@ -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); + } +} diff --git a/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/container.xml b/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/container.xml new file mode 100644 index 000000000..410c81422 --- /dev/null +++ b/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/container.xml @@ -0,0 +1,16 @@ + + + + + + + + + + +