diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/AuthenticatingRedisClient.java b/src/main/java/org/springframework/data/redis/connection/lettuce/AuthenticatingRedisClient.java new file mode 100644 index 000000000..fad5e3139 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/AuthenticatingRedisClient.java @@ -0,0 +1,65 @@ +/* + * 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.data.redis.connection.lettuce; + +import com.lambdaworks.redis.RedisAsyncConnection; +import com.lambdaworks.redis.RedisClient; +import com.lambdaworks.redis.RedisConnection; +import com.lambdaworks.redis.codec.RedisCodec; +import com.lambdaworks.redis.pubsub.RedisPubSubConnection; + +/** + * Extension of {@link RedisClient} that calls auth on all new connections using + * the supplied credentials + * + * @author Jennifer Hickey + * + */ +public class AuthenticatingRedisClient extends RedisClient { + + private String password; + + public AuthenticatingRedisClient(String host, int port, String password) { + super(host, port); + this.password = password; + } + + public AuthenticatingRedisClient(String host, String password) { + super(host); + this.password = password; + } + + @Override + public RedisConnection connect(RedisCodec codec) { + RedisConnection conn = super.connect(codec); + conn.auth(password); + return conn; + } + + @Override + public RedisAsyncConnection connectAsync(RedisCodec codec) { + RedisAsyncConnection conn = super.connectAsync(codec); + conn.auth(password); + return conn; + } + + @Override + public RedisPubSubConnection connectPubSub(RedisCodec codec) { + RedisPubSubConnection conn = super.connectPubSub(codec); + conn.auth(password); + return conn; + } +} diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/DefaultLettucePool.java b/src/main/java/org/springframework/data/redis/connection/lettuce/DefaultLettucePool.java index 40852605a..0cc02f8b1 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/DefaultLettucePool.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/DefaultLettucePool.java @@ -20,7 +20,9 @@ import java.util.concurrent.TimeUnit; import org.apache.commons.pool.BasePoolableObjectFactory; import org.apache.commons.pool.impl.GenericObjectPool; import org.apache.commons.pool.impl.GenericObjectPool.Config; +import org.springframework.beans.factory.InitializingBean; import org.springframework.data.redis.connection.PoolException; +import org.springframework.util.Assert; import com.lambdaworks.redis.RedisAsyncConnection; import com.lambdaworks.redis.RedisClient; @@ -31,10 +33,22 @@ import com.lambdaworks.redis.RedisClient; * @author Jennifer Hickey * */ -public class DefaultLettucePool implements LettucePool { - private final GenericObjectPool internalPool; - private static final int DEFAULT_TIMEOUT = (int) TimeUnit.MILLISECONDS.convert(60, TimeUnit.SECONDS); +public class DefaultLettucePool implements LettucePool, InitializingBean { + private GenericObjectPool internalPool; private RedisClient client; + private int dbIndex = 0; + private Config poolConfig = new Config(); + private String hostName = "localhost"; + private int port = 6379; + private String password; + private long timeout = TimeUnit.MILLISECONDS.convert(60, TimeUnit.SECONDS); + + /** + * Constructs a new DefaultLettucePool instance with + * default settings. + */ + public DefaultLettucePool() { + } /** * Uses the {@link Config} and {@link RedisClient} defaults for configuring @@ -46,7 +60,8 @@ public class DefaultLettucePool implements LettucePool { * The Redis port */ public DefaultLettucePool(String hostName, int port) { - this(hostName, port, 0, DEFAULT_TIMEOUT, new Config()); + this.hostName = hostName; + this.port = port; } /** @@ -60,84 +75,17 @@ public class DefaultLettucePool implements LettucePool { * The pool {@link Config} */ public DefaultLettucePool(String hostName, int port, Config poolConfig) { - this(hostName, port, 0, DEFAULT_TIMEOUT, poolConfig); + this.hostName = hostName; + this.port = port; + this.poolConfig = poolConfig; } - /** - * - * Uses the {@link Config} defaults for configuring the connection pool - * - * @param client - * The {@link RedisClient} for connecting to Redis - * - */ - public DefaultLettucePool(RedisClient client) { - this.client = client; - this.internalPool = new GenericObjectPool(new LettuceFactory(client, 0), new Config()); - } - - /** - * - * @param client - * The {@link RedisClient} for connecting to Redis - * - * @param poolConfig - * The pool {@link Config} - * @param dbIndex - * The index of the database all connections should use. The - * database will be selected when connections are activated. - */ - public DefaultLettucePool(RedisClient client, Config poolConfig, int dbIndex) { - this.client = client; - this.internalPool = new GenericObjectPool(new LettuceFactory(client, dbIndex), poolConfig); - } - - /** - * Uses the {@link Config} defaults for configuring the connection pool - * - * @param hostName - * The Redis host - * @param port - * The Redis port - * @param dbIndex - * The index of the database all connections should use. The - * database will be selected when connections are activated. - * @param password - * The password used for authenticating with the Redis server or - * null if no password required - * @param timeout - * The socket timeout or 0 to use the default socket timeout - */ - public DefaultLettucePool(String hostName, int port, int dbIndex, int timeout) { - this(hostName, port, dbIndex, timeout, new Config()); - } - - /** - * - * @param hostName - * The Redis host - * @param port - * The Redis port - * @param dbIndex - * The index of the database all connections should use. The - * database will be selected when connections are activated. - * @param password - * The password used for authenticating with the Redis server or - * null if no password required - * @param timeout - * The socket timeout or 0 to use the default socket timeout - * @param poolConfig - * The pool {@link COnfig} - */ - public DefaultLettucePool(String hostName, int port, int dbIndex, int timeout, Config poolConfig) { - this.client = new RedisClient(hostName, port); + public void afterPropertiesSet() { + this.client = password != null ? new AuthenticatingRedisClient(hostName, port, password) : + new RedisClient(hostName, port); client.setDefaultTimeout(timeout, TimeUnit.MILLISECONDS); this.internalPool = new GenericObjectPool(new LettuceFactory(client, dbIndex), poolConfig); } - - public RedisClient getClient() { - return client; - } @SuppressWarnings("unchecked") public RedisAsyncConnection getResource() { @@ -172,6 +120,122 @@ public class DefaultLettucePool implements LettucePool { } } + public RedisClient getClient() { + return client; + } + + /** + * + * @return The pool configuration + */ + public Config getPoolConfig() { + return poolConfig; + } + + /** + * + * @param poolConfig The pool configuration to use + */ + public void setPoolConfig(Config poolConfig) { + this.poolConfig = poolConfig; + } + + /** + * Returns the index of the database. + * + * @return Returns the database index + */ + public int getDatabase() { + return dbIndex; + } + + /** + * Sets the index of the database used by this connection pool. Default + * is 0. + * + * @param index + * database index + */ + public void setDatabase(int index) { + Assert.isTrue(index >= 0, "invalid DB index (a positive index required)"); + this.dbIndex = index; + } + + /** + * Returns the password used for authenticating with the Redis server. + * + * @return password for authentication + */ + public String getPassword() { + return password; + } + + /** + * Sets the password used for authenticating with the Redis server. + * + * @param password the password to set + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Returns the current host. + * + * @return the host + */ + public String getHostName() { + return hostName; + } + + /** + * Sets the host. + * + * @param host + * the host to set + */ + public void setHostName(String host) { + this.hostName = host; + } + + /** + * Returns the current port. + * + * @return the port + */ + public int getPort() { + return port; + } + + /** + * Sets the port. + * + * @param port + * the port to set + */ + public void setPort(int port) { + this.port = port; + } + + /** + * Returns the connection timeout (in milliseconds). + * + * @return connection timeout + */ + public long getTimeout() { + return timeout; + } + + /** + * Sets the connection timeout (in milliseconds). + * + * @param timeout + * connection timeout + */ + public void setTimeout(long timeout) { + this.timeout = timeout; + } + private static class LettuceFactory extends BasePoolableObjectFactory { private final RedisClient client; diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java index 70f3a850e..ee6674cd7 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java @@ -70,6 +70,7 @@ public class LettuceConnectionFactory implements InitializingBean, DisposableBea private int dbIndex = 0; /** Synchronization monitor for the shared Connection */ private final Object connectionMonitor = new Object(); + private String password; /** * Constructs a new LettuceConnectionFactory instance with @@ -278,6 +279,24 @@ public class LettuceConnectionFactory implements InitializingBean, DisposableBea this.dbIndex = index; } + /** + * Returns the password used for authenticating with the Redis server. + * + * @return password for authentication + */ + public String getPassword() { + return password; + } + + /** + * Sets the password used for authenticating with the Redis server. + * + * @param password the password to set + */ + public void setPassword(String password) { + this.password = password; + } + protected RedisAsyncConnection getSharedConnection() { if (shareNativeConnection) { synchronized (this.connectionMonitor) { @@ -314,7 +333,8 @@ public class LettuceConnectionFactory implements InitializingBean, DisposableBea if(pool != null) { return pool.getClient(); } - RedisClient client = new RedisClient(hostName, port); + RedisClient client = password != null ? new AuthenticatingRedisClient(hostName, port, password) : + new RedisClient(hostName, port); client.setDefaultTimeout(timeout, TimeUnit.MILLISECONDS); return client; } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/AuthenticatingRedisClientTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/AuthenticatingRedisClientTests.java new file mode 100644 index 000000000..08a204716 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/AuthenticatingRedisClientTests.java @@ -0,0 +1,87 @@ +/* + * 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.data.redis.connection.lettuce; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import com.lambdaworks.redis.RedisAsyncConnection; +import com.lambdaworks.redis.RedisClient; +import com.lambdaworks.redis.RedisConnection; +import com.lambdaworks.redis.RedisException; +import com.lambdaworks.redis.pubsub.RedisPubSubConnection; + +/** + * Integration test of {@link AuthenticatingRedisClient}. Enable requirepass and + * comment out the @Ignore to run. + * + * @author Jennifer Hickey + * + */ +@Ignore("Redis must have requirepass set to run this test") +public class AuthenticatingRedisClientTests { + + private RedisClient client; + + @Before + public void setUp() { + client = new AuthenticatingRedisClient("localhost", "foo"); + } + + @Test + public void connect() { + RedisConnection conn = client.connect(); + conn.ping(); + } + + @Test(expected = RedisException.class) + public void connectWithInvalidPassword() { + RedisClient badClient = new AuthenticatingRedisClient("localhost", "notthepassword"); + badClient.connect(); + } + + @Test + public void codecConnect() { + RedisConnection conn = client.connect(LettuceUtils.CODEC); + conn.ping(); + } + + @Test + public void connectAsync() { + RedisAsyncConnection conn = client.connectAsync(); + conn.ping(); + } + + @Test + public void codecConnectAsync() { + RedisAsyncConnection conn = client.connectAsync(LettuceUtils.CODEC); + conn.ping(); + } + + @Test + public void connectPubSub() { + RedisPubSubConnection conn = client.connectPubSub(); + conn.ping(); + } + + @Test + public void codecConnectPubSub() { + RedisPubSubConnection conn = client.connectPubSub(LettuceUtils.CODEC); + conn.ping(); + } + +} diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/DefaultLettucePoolTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/DefaultLettucePoolTests.java index 3fd541856..b3149800c 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/DefaultLettucePoolTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/DefaultLettucePoolTests.java @@ -21,14 +21,13 @@ import static org.junit.Assert.fail; import org.apache.commons.pool.impl.GenericObjectPool.Config; import org.junit.After; -import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.springframework.data.redis.SettingsUtils; import org.springframework.data.redis.connection.PoolConfig; import org.springframework.data.redis.connection.PoolException; import com.lambdaworks.redis.RedisAsyncConnection; -import com.lambdaworks.redis.RedisClient; import com.lambdaworks.redis.RedisException; /** @@ -39,25 +38,19 @@ import com.lambdaworks.redis.RedisException; */ public class DefaultLettucePoolTests { - private RedisClient client; - private DefaultLettucePool pool; - @Before - public void setUp() { - this.client = new RedisClient(SettingsUtils.getHost(), SettingsUtils.getPort()); - } - @After public void tearDown() { - if (this.pool != null) { + if(this.pool != null) { this.pool.destroy(); } } @Test public void testGetResource() { - this.pool = new DefaultLettucePool(client); + this.pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort()); + pool.afterPropertiesSet(); RedisAsyncConnection client = pool.getResource(); assertNotNull(client); client.ping(); @@ -68,7 +61,8 @@ public class DefaultLettucePoolTests { Config poolConfig = new Config(); poolConfig.maxActive = 1; poolConfig.maxWait = 1; - this.pool = new DefaultLettucePool(client, poolConfig, 0); + this.pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort(), poolConfig); + pool.afterPropertiesSet(); RedisAsyncConnection client = pool.getResource(); assertNotNull(client); try { @@ -82,14 +76,16 @@ public class DefaultLettucePoolTests { public void testGetResourceValidate() { PoolConfig poolConfig = new PoolConfig(); poolConfig.setTestOnBorrow(true); - this.pool = new DefaultLettucePool(client, poolConfig, 0); + this.pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort(), poolConfig); + pool.afterPropertiesSet(); RedisAsyncConnection client = pool.getResource(); assertNotNull(client); } @Test(expected = PoolException.class) - public void testGetResourceCreationUnsuccessful() { - this.pool = new DefaultLettucePool(new RedisClient(SettingsUtils.getHost(), 3333)); + public void testGetResourceCreationUnsuccessful() throws Exception { + this.pool = new DefaultLettucePool(SettingsUtils.getHost(), 3333); + pool.afterPropertiesSet(); pool.getResource(); } @@ -98,7 +94,8 @@ public class DefaultLettucePoolTests { Config poolConfig = new Config(); poolConfig.maxActive = 1; poolConfig.maxWait = 1; - this.pool = new DefaultLettucePool(client); + this.pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort(), poolConfig); + pool.afterPropertiesSet(); RedisAsyncConnection client = pool.getResource(); assertNotNull(client); pool.returnResource(client); @@ -110,7 +107,8 @@ public class DefaultLettucePoolTests { Config poolConfig = new Config(); poolConfig.maxActive = 1; poolConfig.maxWait = 1; - this.pool = new DefaultLettucePool(client, poolConfig, 0); + this.pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort(), poolConfig); + pool.afterPropertiesSet(); RedisAsyncConnection client = pool.getResource(); assertNotNull(client); pool.returnBrokenResource(client); @@ -123,21 +121,46 @@ public class DefaultLettucePoolTests { } } - @Test - public void testCreateWithHostAndPort() { - this.pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort()); - assertNotNull(pool.getResource()); - } - @Test public void testCreateWithDbIndex() { - this.pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort(), 1, 65000); + this.pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort()); + pool.setDatabase(1); + pool.afterPropertiesSet(); assertNotNull(pool.getResource()); } @Test(expected = PoolException.class) public void testCreateWithDbIndexInvalid() { - this.pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort(), 17, 65000); + this.pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort()); + pool.setDatabase(17); + pool.afterPropertiesSet(); + pool.getResource(); + } + + @Test(expected = PoolException.class) + public void testCreateWithPasswordNoPassword() { + this.pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort()); + pool.setPassword("notthepassword"); + pool.afterPropertiesSet(); + pool.getResource(); + } + + @Ignore("Redis must have requirepass set to run this test") + @Test + public void testCreatePassword() { + this.pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort()); + pool.setPassword("foo"); + pool.afterPropertiesSet(); + RedisAsyncConnection conn = pool.getResource(); + conn.ping(); + } + + @Ignore("Redis must have requirepass set to run this test") + @Test(expected = PoolException.class) + public void testCreateInvalidPassword() { + this.pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort()); + pool.setPassword("bad"); + pool.afterPropertiesSet(); pool.getResource(); } } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryTests.java index 62c2b4bf0..017db1cc6 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryTests.java @@ -221,8 +221,9 @@ public class LettuceConnectionFactoryTests { Config poolConfig = new Config(); poolConfig.maxActive = 1; poolConfig.maxWait = 1; - LettuceConnectionFactory factory2 = new LettuceConnectionFactory(new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort(), - poolConfig)); + DefaultLettucePool pool = new DefaultLettucePool(SettingsUtils.getHost(), SettingsUtils.getPort(), poolConfig); + pool.afterPropertiesSet(); + LettuceConnectionFactory factory2 = new LettuceConnectionFactory(pool); factory2.afterPropertiesSet(); factory2.createLettuceConnector(false); try { @@ -250,4 +251,15 @@ public class LettuceConnectionFactoryTests { } Thread.sleep(234234234); } + + @Ignore("Redis must have requirepass set to run this test") + @Test + public void testConnectWithPassword() { + factory.setPassword("foo"); + factory.afterPropertiesSet(); + RedisConnection conn = factory.getConnection(); + // Test shared and dedicated conns + conn.ping(); + conn.bLPop(1, "key".getBytes()); + } } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionIntegrationTests.java index 60378e489..baf3f26cb 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionIntegrationTests.java @@ -127,8 +127,10 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra @Test public void testClosePooledConnectionWithShared() { - LettuceConnectionFactory factory2 = new LettuceConnectionFactory(new DefaultLettucePool(SettingsUtils.getHost(), - SettingsUtils.getPort())); + DefaultLettucePool pool = new DefaultLettucePool(SettingsUtils.getHost(), + SettingsUtils.getPort()); + pool.afterPropertiesSet(); + LettuceConnectionFactory factory2 = new LettuceConnectionFactory(pool); factory2.afterPropertiesSet(); RedisConnection connection = factory2.getConnection(); // Use the connection to make sure the channel is initialized, else nothing happens on close @@ -144,8 +146,10 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra @Test public void testClosePooledConnectionNotShared() { - LettuceConnectionFactory factory2 = new LettuceConnectionFactory(new DefaultLettucePool(SettingsUtils.getHost(), - SettingsUtils.getPort())); + DefaultLettucePool pool = new DefaultLettucePool(SettingsUtils.getHost(), + SettingsUtils.getPort()); + pool.afterPropertiesSet(); + LettuceConnectionFactory factory2 = new LettuceConnectionFactory(pool); factory2.setShareNativeConnection(false); factory2.afterPropertiesSet(); RedisConnection connection = factory2.getConnection(); @@ -178,8 +182,10 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra @SuppressWarnings("rawtypes") @Test public void testCloseReturnBrokenResourceToPool() { - LettuceConnectionFactory factory2 = new LettuceConnectionFactory(new DefaultLettucePool(SettingsUtils.getHost(), - SettingsUtils.getPort())); + DefaultLettucePool pool = new DefaultLettucePool(SettingsUtils.getHost(), + SettingsUtils.getPort()); + pool.afterPropertiesSet(); + LettuceConnectionFactory factory2 = new LettuceConnectionFactory(pool); factory2.setShareNativeConnection(false); factory2.afterPropertiesSet(); RedisConnection connection = factory2.getConnection(); @@ -202,8 +208,10 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra @Test public void testSelectNotShared() { - LettuceConnectionFactory factory2 = new LettuceConnectionFactory(new DefaultLettucePool(SettingsUtils.getHost(), - SettingsUtils.getPort())); + DefaultLettucePool pool = new DefaultLettucePool(SettingsUtils.getHost(), + SettingsUtils.getPort()); + pool.afterPropertiesSet(); + LettuceConnectionFactory factory2 = new LettuceConnectionFactory(pool); factory2.setShareNativeConnection(false); factory2.afterPropertiesSet(); RedisConnection connection = factory2.getConnection();