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 new file mode 100644 index 000000000..22b44ddc3 --- /dev/null +++ b/spring-data-redis/src/main/java/org/springframework/data/keyvalue/redis/support/collections/RedisProperties.java @@ -0,0 +1,265 @@ +/* + * 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.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +import org.springframework.data.keyvalue.redis.connection.DataType; +import org.springframework.data.keyvalue.redis.core.BoundHashOperations; +import org.springframework.data.keyvalue.redis.core.RedisOperations; + +/** + * {@link Properties} extension for a Redis back-store. Useful for reading (and storing) properties + * inside a Redis hash. Particularly useful inside a Spring container for hooking into Spring's property + * placeholder or {@link org.springframework.beans.factory.config.PropertiesFactoryBean}. + *

+ * Note that this implementation only accepts Strings - objects of other type are not supported. + * + * @see Properties + * @see org.springframework.core.io.support.PropertiesLoaderSupport + * @author Costin Leau + */ +public class RedisProperties extends Properties implements RedisMap { + + private final BoundHashOperations hashOps; + private final RedisMap delegate; + + /** + * Constructs a new RedisProperties instance. + * + */ + public RedisProperties(BoundHashOperations boundOps) { + this(null, boundOps); + } + + /** + * Constructs a new RedisProperties instance. + * + * @param boundOps + */ + public RedisProperties(String key, RedisOperations operations) { + this(null, operations. boundHashOps(key)); + } + + /** + * Constructs a new RedisProperties instance. + * + * @param defaults + */ + public RedisProperties(Properties defaults, BoundHashOperations boundOps) { + super(defaults); + this.hashOps = boundOps; + this.delegate = new DefaultRedisMap(boundOps); + } + + /** + * Constructs a new RedisProperties instance. + * + * @param defaults + * @param boundOps + */ + public RedisProperties(Properties defaults, String key, RedisOperations operations) { + this(defaults, operations. boundHashOps(key)); + } + + @Override + public synchronized Object get(Object key) { + return delegate.get(key); + } + + @Override + public synchronized Object put(Object key, Object value) { + return delegate.put((String) key, (String) value); + } + + @SuppressWarnings("unchecked") + @Override + public synchronized void putAll(Map t) { + delegate.putAll((Map) t); + } + + @Override + public Enumeration propertyNames() { + Set keys = new LinkedHashSet(delegate.keySet()); + keys.addAll(defaults.stringPropertyNames()); + return Collections.enumeration(keys); + } + + @Override + public synchronized void clear() { + delegate.clear(); + } + + @Override + public synchronized Object clone() { + return new RedisProperties(defaults, hashOps); + } + + @Override + public synchronized boolean contains(Object value) { + return containsValue(value); + } + + @Override + public synchronized boolean containsKey(Object key) { + return delegate.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + @SuppressWarnings("unchecked") + @Override + public synchronized Enumeration elements() { + Collection values = delegate.values(); + return Collections.enumeration(values); + } + + @Override + @SuppressWarnings("unchecked") + public Set> entrySet() { + Set entries = delegate.entrySet(); + return entries; + } + + @Override + public synchronized boolean equals(Object o) { + if (o == this) + return true; + + if (o instanceof RedisProperties) { + return o.hashCode() == hashCode(); + } + return false; + } + + @Override + public synchronized int hashCode() { + int hash = RedisProperties.class.hashCode(); + return hash * 17 + delegate.hashCode(); + } + + @Override + public synchronized boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public synchronized Enumeration keys() { + Set keys = keySet(); + return Collections.enumeration(keys); + } + + @SuppressWarnings("unchecked") + @Override + public Set keySet() { + Set keys = delegate.keySet(); + return keys; + } + + @Override + public synchronized Object remove(Object key) { + return delegate.remove(key); + } + + @Override + public synchronized int size() { + return delegate.size(); + } + + @SuppressWarnings("unchecked") + @Override + public Collection values() { + Collection vals = delegate.values(); + return vals; + } + + @Override + public Long increment(Object key, long delta) { + return hashOps.increment((String) key, delta); + } + + @Override + public RedisOperations getOperations() { + return hashOps.getOperations(); + } + + @Override + public Boolean expire(long timeout, TimeUnit unit) { + return hashOps.expire(timeout, unit); + } + + @Override + public Boolean expireAt(Date date) { + return hashOps.expireAt(date); + } + + @Override + public Long getExpire() { + return hashOps.getExpire(); + } + + @Override + public String getKey() { + return hashOps.getKey(); + } + + @Override + public DataType getType() { + return hashOps.getType(); + } + + @Override + public Boolean persist() { + return hashOps.persist(); + } + + @Override + public void rename(String newKey) { + hashOps.rename(newKey); + } + + @Override + public Object putIfAbsent(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean replace(Object key, Object oldValue, Object newValue) { + throw new UnsupportedOperationException(); + } + + @Override + public Object replace(Object key, Object value) { + throw new UnsupportedOperationException(); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/AbstractRedisMapTests.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/AbstractRedisMapTests.java index 1218c2e68..f9d266355 100644 --- a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/AbstractRedisMapTests.java +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/AbstractRedisMapTests.java @@ -162,7 +162,7 @@ public abstract class AbstractRedisMapTests { K k1 = getKey(); V v1 = getValue(); - assertNull(map.get(UUID.randomUUID())); + assertNull(map.get(UUID.randomUUID().toString())); assertNull(map.get(k1)); map.put(k1, v1); assertEquals(v1, map.get(k1)); diff --git a/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisPropertiesTest.java b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisPropertiesTest.java new file mode 100644 index 000000000..2075d788a --- /dev/null +++ b/spring-data-redis/src/test/java/org/springframework/data/keyvalue/redis/support/collections/RedisPropertiesTest.java @@ -0,0 +1,309 @@ +/* + * 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.junit.Assert.*; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.Properties; +import java.util.Set; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runners.Parameterized.Parameters; +import org.springframework.data.keyvalue.redis.Person; +import org.springframework.data.keyvalue.redis.SettingsUtils; +import org.springframework.data.keyvalue.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.keyvalue.redis.connection.jredis.JredisConnectionFactory; +import org.springframework.data.keyvalue.redis.connection.rjc.RjcConnectionFactory; +import org.springframework.data.keyvalue.redis.core.RedisTemplate; +import org.springframework.data.keyvalue.redis.core.StringRedisTemplate; +import org.springframework.data.keyvalue.redis.serializer.JacksonJsonRedisSerializer; +import org.springframework.data.keyvalue.redis.serializer.OxmSerializer; +import org.springframework.oxm.xstream.XStreamMarshaller; + +/** + * @author Costin Leau + */ +public class RedisPropertiesTest extends RedisMapTests { + + protected Properties defaults = new Properties(); + protected RedisProperties props; + + /** + * Constructs a new RedisPropertiesTest instance. + * + * @param keyFactory + * @param valueFactory + * @param template + */ + public RedisPropertiesTest(ObjectFactory keyFactory, ObjectFactory valueFactory, + RedisTemplate template) { + super(keyFactory, valueFactory, template); + } + + @Override + RedisMap createMap() { + String redisName = getClass().getSimpleName(); + props = new RedisProperties(defaults, redisName, new StringRedisTemplate(template.getConnectionFactory())); + return props; + } + + @Override + protected RedisStore copyStore(RedisStore store) { + return new RedisProperties(store.getKey(), store.getOperations()); + } + + @Test + public void testGetOperations() { + assertTrue(map.getOperations() instanceof StringRedisTemplate); + } + + @Test + public void testPropertiesLoad() throws Exception { + InputStream stream = getClass().getResourceAsStream( + "/org/springframework/data/keyvalue/redis/support/collections/props.properties"); + + assertNotNull(stream); + + int size = props.size(); + + try { + props.load(stream); + } finally { + stream.close(); + } + + assertEquals("bar", props.get("foo")); + assertEquals("head", props.get("bucket")); + assertEquals("island", props.get("lotus")); + assertEquals(size + 3, props.size()); + } + + @Test + @Ignore + public void testPropertiesLoadXml() throws Exception { + InputStream stream = getClass().getResourceAsStream( + "/org/springframework/data/keyvalue/redis/support/collections/props.properties"); + + assertNotNull(stream); + + int size = props.size(); + + try { + props.loadFromXML(stream); + } finally { + stream.close(); + } + + assertEquals("bar", props.get("foo")); + assertEquals("head", props.get("bucket")); + assertEquals("island", props.get("lotus")); + assertEquals(size + 3, props.size()); + } + + @Test + public void testPropertiesSave() throws Exception { + props.setProperty("x", "y"); + props.setProperty("a", "b"); + + StringWriter writer = new StringWriter(); + props.store(writer, "no-comment"); + System.out.println(writer.toString()); + } + + @Test + @Ignore + public void testPropertiesSaveXml() throws Exception { + props.setProperty("x", "y"); + props.setProperty("a", "b"); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + props.storeToXML(bos, "comment"); + System.out.println(bos.toString()); + } + + @Test + public void testGetProperty() throws Exception { + String property = props.getProperty("a"); + assertNull(property); + defaults.put("a", "x"); + assertEquals("x", props.getProperty("a")); + } + + @Test + public void testGetPropertyDefault() throws Exception { + assertEquals("x", props.getProperty("a", "x")); + } + + @Test + public void testSetProperty() throws Exception { + assertNull(props.getProperty("a")); + defaults.setProperty("a", "x"); + assertEquals("x", props.getProperty("a")); + } + + @Test + public void testPropertiesList() throws Exception { + defaults.setProperty("a", "b"); + props.setProperty("x", "y"); + props.list(System.out); + } + + @Test + public void testPropertyNames() throws Exception { + String key1="foo"; + String key2="x"; + String key3 = "d"; + + String val ="o"; + + defaults.setProperty(key3, val); + props.setProperty(key1, val); + props.setProperty(key2, val); + + Enumeration names = props.propertyNames(); + Set keys = new LinkedHashSet(); + keys.add(names.nextElement()); + keys.add(names.nextElement()); + keys.add(names.nextElement()); + + assertFalse(names.hasMoreElements()); + } + + @Test + public void testStringPropertyNames() throws Exception { + String key1 = "foo"; + String key2 = "x"; + String key3 = "d"; + + String val = "o"; + + defaults.setProperty(key3, val); + props.setProperty(key1, val); + props.setProperty(key2, val); + + Set keys = props.stringPropertyNames(); + assertTrue(keys.contains(key1)); + assertTrue(keys.contains(key2)); + assertTrue(keys.contains(key3)); + } + + @Parameters + public static Collection testParams() { + // XStream serializer + XStreamMarshaller xstream = new XStreamMarshaller(); + try { + xstream.afterPropertiesSet(); + } catch (Exception ex) { + throw new RuntimeException("Cannot init XStream", ex); + } + OxmSerializer serializer = new OxmSerializer(xstream, xstream); + JacksonJsonRedisSerializer jsonSerializer = new JacksonJsonRedisSerializer(Person.class); + JacksonJsonRedisSerializer jsonStringSerializer = new JacksonJsonRedisSerializer(String.class); + + // create Jedis Factory + ObjectFactory stringFactory = new StringObjectFactory(); + + JedisConnectionFactory jedisConnFactory = new JedisConnectionFactory(); + jedisConnFactory.setUsePool(false); + + jedisConnFactory.setPort(SettingsUtils.getPort()); + jedisConnFactory.setHostName(SettingsUtils.getHost()); + + jedisConnFactory.afterPropertiesSet(); + + RedisTemplate genericTemplate = new RedisTemplate(jedisConnFactory); + + RedisTemplate xstreamGenericTemplate = new RedisTemplate(); + xstreamGenericTemplate.setConnectionFactory(jedisConnFactory); + xstreamGenericTemplate.setDefaultSerializer(serializer); + xstreamGenericTemplate.afterPropertiesSet(); + + RedisTemplate jsonPersonTemplate = new RedisTemplate(); + jsonPersonTemplate.setConnectionFactory(jedisConnFactory); + jsonPersonTemplate.setDefaultSerializer(jsonSerializer); + jsonPersonTemplate.setHashKeySerializer(jsonSerializer); + jsonPersonTemplate.setHashValueSerializer(jsonStringSerializer); + jsonPersonTemplate.afterPropertiesSet(); + + // JRedis + JredisConnectionFactory jredisConnFactory = new JredisConnectionFactory(); + jredisConnFactory.setUsePool(true); + jredisConnFactory.setPort(SettingsUtils.getPort()); + jredisConnFactory.setHostName(SettingsUtils.getHost()); + jredisConnFactory.afterPropertiesSet(); + + RedisTemplate genericTemplateJR = new RedisTemplate(jredisConnFactory); + RedisTemplate xGenericTemplateJR = new RedisTemplate(); + xGenericTemplateJR.setConnectionFactory(jredisConnFactory); + xGenericTemplateJR.setDefaultSerializer(serializer); + xGenericTemplateJR.afterPropertiesSet(); + + RedisTemplate jsonPersonTemplateJR = new RedisTemplate(); + jsonPersonTemplateJR.setConnectionFactory(jredisConnFactory); + jsonPersonTemplateJR.setDefaultSerializer(jsonSerializer); + jsonPersonTemplateJR.setHashKeySerializer(jsonSerializer); + jsonPersonTemplateJR.setHashValueSerializer(jsonStringSerializer); + jsonPersonTemplateJR.afterPropertiesSet(); + + // RJC + + // rjc + RjcConnectionFactory rjcConnFactory = new RjcConnectionFactory(); + rjcConnFactory.setUsePool(true); + rjcConnFactory.setPort(SettingsUtils.getPort()); + rjcConnFactory.setHostName(SettingsUtils.getHost()); + rjcConnFactory.afterPropertiesSet(); + + RedisTemplate genericTemplateRJC = new RedisTemplate(jredisConnFactory); + RedisTemplate xGenericTemplateRJC = new RedisTemplate(); + xGenericTemplateRJC.setConnectionFactory(rjcConnFactory); + xGenericTemplateRJC.setDefaultSerializer(serializer); + xGenericTemplateRJC.afterPropertiesSet(); + + RedisTemplate jsonPersonTemplateRJC = new RedisTemplate(); + jsonPersonTemplateRJC.setConnectionFactory(rjcConnFactory); + jsonPersonTemplateRJC.setDefaultSerializer(jsonSerializer); + jsonPersonTemplateRJC.setHashKeySerializer(jsonSerializer); + jsonPersonTemplateRJC.setHashValueSerializer(jsonStringSerializer); + jsonPersonTemplateRJC.afterPropertiesSet(); + + + return Arrays.asList(new Object[][] { { stringFactory, stringFactory, genericTemplate }, + { stringFactory, stringFactory, genericTemplate }, { stringFactory, stringFactory, genericTemplate }, + { stringFactory, stringFactory, genericTemplate }, + { stringFactory, stringFactory, xstreamGenericTemplate }, + { stringFactory, stringFactory, genericTemplateJR }, + { stringFactory, stringFactory, genericTemplateJR }, + { stringFactory, stringFactory, genericTemplateJR }, + { stringFactory, stringFactory, genericTemplateJR }, + { stringFactory, stringFactory, xGenericTemplateJR }, + { stringFactory, stringFactory, jsonPersonTemplate }, + { stringFactory, stringFactory, jsonPersonTemplateJR }, + { stringFactory, stringFactory, genericTemplateRJC }, + { stringFactory, stringFactory, genericTemplateRJC }, + { stringFactory, stringFactory, genericTemplateRJC }, + { stringFactory, stringFactory, genericTemplateRJC }, + { stringFactory, stringFactory, xGenericTemplateRJC }, + { stringFactory, stringFactory, jsonPersonTemplateRJC } }); + } +} \ No newline at end of file diff --git a/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/props.properties b/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/props.properties new file mode 100644 index 000000000..aad78142d --- /dev/null +++ b/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/props.properties @@ -0,0 +1,4 @@ +# redis connection properties +foo=bar +bucket=head +lotus=island \ No newline at end of file diff --git a/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/props.xml b/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/props.xml new file mode 100644 index 000000000..2e49de5b7 --- /dev/null +++ b/spring-data-redis/src/test/resources/org/springframework/data/keyvalue/redis/support/collections/props.xml @@ -0,0 +1,6 @@ + +Hi +bar +head +island + \ No newline at end of file