From 28a525acecc94dc9a8acdd289dea6ffab48d60d4 Mon Sep 17 00:00:00 2001 From: Ben Hale Date: Fri, 3 Nov 2017 09:01:22 -0700 Subject: [PATCH] Improve Key-Value Backwards Compatibility Currently, the entire Spring Service Connector implementation is backwards compatible to Spring Data 1 except for the Key Value (Redis) services. This is broken because the Spring Data 2 Redis API has been improved. It turns out that the Spring 2 Redis API doesn't remove the previous Spring Data 1 variant and therefore, the two can coexist. This change reinstates the previous Spring Data 1 Redis implementation and adds it to the list of ServiceConnectorCreator services. If the classpath contains the Spring Data 2 variant, then only the Spring Data 2 Creator will be used. If the classpath contains the Spring Data 1 variant, then only the Spring Data 1 Creator will be used. --- ...Data1RedisConnectionFactoryConfigurer.java | 56 +++++++++++++++ ...ingData1RedisConnectionFactoryCreator.java | 70 +++++++++++++++++++ ...disLettuceConnectionFactoryConfigurer.java | 28 ++++++++ ...work.cloud.service.ServiceConnectorCreator | 1 + ...ata1RedisConnectionFactoryFactoryTest.java | 33 +++++++++ ...Data1RedisServiceConnectorCreatorTest.java | 60 ++++++++++++++++ 6 files changed, 248 insertions(+) create mode 100644 spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/keyval/SpringData1RedisConnectionFactoryConfigurer.java create mode 100644 spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/keyval/SpringData1RedisConnectionFactoryCreator.java create mode 100644 spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/keyval/SpringData1RedisLettuceConnectionFactoryConfigurer.java create mode 100644 spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/redis/SpringData1RedisConnectionFactoryFactoryTest.java create mode 100644 spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/redis/SpringData1RedisServiceConnectorCreatorTest.java diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/keyval/SpringData1RedisConnectionFactoryConfigurer.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/keyval/SpringData1RedisConnectionFactoryConfigurer.java new file mode 100644 index 0000000..84116f9 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/keyval/SpringData1RedisConnectionFactoryConfigurer.java @@ -0,0 +1,56 @@ +package org.springframework.cloud.service.keyval; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.cloud.service.MapServiceConnectionConfigurer; +import org.springframework.cloud.service.MapServiceConnectorConfig; +import org.springframework.cloud.service.PooledServiceConnectorConfig; +import org.springframework.cloud.service.ServiceConnectorConfigurer; +import org.springframework.cloud.service.Util; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; + +import redis.clients.jedis.JedisPoolConfig; + +/** + * + * @author Ramnivas Laddad + * @author Scott Frederick + * + */ +public class SpringData1RedisConnectionFactoryConfigurer implements ServiceConnectorConfigurer { + private MapServiceConnectionConfigurer mapServiceConnectionConfigurer = + new MapServiceConnectionConfigurer(); + + @Override + public JedisConnectionFactory configure(JedisConnectionFactory connectionFactory, RedisConnectionFactoryConfig config) { + if (config != null) { + configurePool(connectionFactory, config); + configureConnection(connectionFactory, config); + } + return connectionFactory; + } + + public JedisConnectionFactory configure(JedisConnectionFactory connectionFactory, PooledServiceConnectorConfig config) { + if (config != null) { + configurePool(connectionFactory, config); + } + return connectionFactory; + } + + private void configurePool(JedisConnectionFactory connectionFactory, PooledServiceConnectorConfig config) { + if (config.getPoolConfig() != null) { + JedisPoolConfig poolConfig = new JedisPoolConfig(); + BeanWrapper target = new BeanWrapperImpl(poolConfig); + BeanWrapper source = new BeanWrapperImpl(config.getPoolConfig()); + Util.setCorrespondingProperties(target, source); + connectionFactory.setPoolConfig(poolConfig); + } + } + + private void configureConnection(JedisConnectionFactory connectionFactory, RedisConnectionFactoryConfig config) { + if (config.getConnectionProperties() != null) { + mapServiceConnectionConfigurer.configure(connectionFactory, config.getConnectionProperties()); + } + } + +} diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/keyval/SpringData1RedisConnectionFactoryCreator.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/keyval/SpringData1RedisConnectionFactoryCreator.java new file mode 100644 index 0000000..b36de47 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/keyval/SpringData1RedisConnectionFactoryCreator.java @@ -0,0 +1,70 @@ +package org.springframework.cloud.service.keyval; + +import static org.springframework.cloud.service.Util.hasClass; + +import org.springframework.cloud.service.AbstractServiceConnectorCreator; +import org.springframework.cloud.service.PooledServiceConnectorConfig; +import org.springframework.cloud.service.ServiceConnectorConfig; +import org.springframework.cloud.service.ServiceConnectorCreationException; +import org.springframework.cloud.service.common.RedisServiceInfo; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; + +/** + * Simplified access to creating Redis service objects. + * Supports Jedis and lettuce Redis clients. + * + * @author Ramnivas Laddad + * @author Jennifer Hickey + * @author Thomas Risberg + * @author Mark Paluch + */ +public class SpringData1RedisConnectionFactoryCreator extends AbstractServiceConnectorCreator { + + private static final String JEDIS_CLASS_NAME = "redis.clients.jedis.Jedis"; + private static final String LETTUCE_CLASS_NAME = "com.lambdaworks.redis.RedisClient"; + + @Override + public RedisConnectionFactory create(RedisServiceInfo serviceInfo, ServiceConnectorConfig serviceConnectorConfig) { + + if (hasClass(JEDIS_CLASS_NAME)) { + + SpringData1RedisConnectionFactoryConfigurer configurer = new SpringData1RedisConnectionFactoryConfigurer(); + + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); + connectionFactory.setHostName(serviceInfo.getHost()); + connectionFactory.setPort(serviceInfo.getPort()); + connectionFactory.setPassword(serviceInfo.getPassword()); + + if (serviceConnectorConfig instanceof RedisConnectionFactoryConfig) { + configurer.configure(connectionFactory, (RedisConnectionFactoryConfig) serviceConnectorConfig); + } else { + configurer.configure(connectionFactory, (PooledServiceConnectorConfig) serviceConnectorConfig); + } + + connectionFactory.afterPropertiesSet(); + return connectionFactory; + } + else if (hasClass(LETTUCE_CLASS_NAME)) { + + SpringData1RedisLettuceConnectionFactoryConfigurer configurer = new SpringData1RedisLettuceConnectionFactoryConfigurer(); + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(); + connectionFactory.setHostName(serviceInfo.getHost()); + connectionFactory.setPort(serviceInfo.getPort()); + connectionFactory.setPassword(serviceInfo.getPassword()); + + configurer.configure(connectionFactory, (RedisConnectionFactoryConfig) serviceConnectorConfig); + + connectionFactory.afterPropertiesSet(); + return connectionFactory; + } + else { + throw new ServiceConnectorCreationException(String.format("Failed to create cloud Redis connection factory " + + "for %s service. No client implementation classes " + + " of jedis or lettuce clients implementation (%s, %s) not found", serviceInfo.getId(), + JEDIS_CLASS_NAME, LETTUCE_CLASS_NAME)); + } + } +} diff --git a/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/keyval/SpringData1RedisLettuceConnectionFactoryConfigurer.java b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/keyval/SpringData1RedisLettuceConnectionFactoryConfigurer.java new file mode 100644 index 0000000..c21338a --- /dev/null +++ b/spring-cloud-spring-service-connector/src/main/java/org/springframework/cloud/service/keyval/SpringData1RedisLettuceConnectionFactoryConfigurer.java @@ -0,0 +1,28 @@ +package org.springframework.cloud.service.keyval; + +import org.springframework.cloud.service.*; + +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; + +/** + * + * @author Mark Paluch + */ +public class SpringData1RedisLettuceConnectionFactoryConfigurer implements ServiceConnectorConfigurer { + private MapServiceConnectionConfigurer mapServiceConnectionConfigurer = + new MapServiceConnectionConfigurer(); + + @Override + public LettuceConnectionFactory configure(LettuceConnectionFactory connectionFactory, RedisConnectionFactoryConfig config) { + if (config != null) { + configureConnection(connectionFactory, config); + } + return connectionFactory; + } + + private void configureConnection(LettuceConnectionFactory connectionFactory, RedisConnectionFactoryConfig config) { + if (config.getConnectionProperties() != null) { + mapServiceConnectionConfigurer.configure(connectionFactory, config.getConnectionProperties()); + } + } +} diff --git a/spring-cloud-spring-service-connector/src/main/resources/META-INF/services/org.springframework.cloud.service.ServiceConnectorCreator b/spring-cloud-spring-service-connector/src/main/resources/META-INF/services/org.springframework.cloud.service.ServiceConnectorCreator index 400024f..7d1a3dd 100644 --- a/spring-cloud-spring-service-connector/src/main/resources/META-INF/services/org.springframework.cloud.service.ServiceConnectorCreator +++ b/spring-cloud-spring-service-connector/src/main/resources/META-INF/services/org.springframework.cloud.service.ServiceConnectorCreator @@ -3,6 +3,7 @@ org.springframework.cloud.service.relational.PostgresqlDataSourceCreator org.springframework.cloud.service.relational.OracleDataSourceCreator org.springframework.cloud.service.relational.DB2DataSourceCreator org.springframework.cloud.service.keyval.RedisConnectionFactoryCreator +org.springframework.cloud.service.keyval.SpringData1RedisConnectionFactoryCreator org.springframework.cloud.service.document.MongoDbFactoryCreator org.springframework.cloud.service.messaging.RabbitConnectionFactoryCreator org.springframework.cloud.service.smtp.MailSenderCreator diff --git a/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/redis/SpringData1RedisConnectionFactoryFactoryTest.java b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/redis/SpringData1RedisConnectionFactoryFactoryTest.java new file mode 100644 index 0000000..0b4c7f0 --- /dev/null +++ b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/redis/SpringData1RedisConnectionFactoryFactoryTest.java @@ -0,0 +1,33 @@ +package org.springframework.cloud.service.redis; + +import org.mockito.Mock; +import org.springframework.cloud.service.AbstractCloudServiceConnectorFactoryTest; +import org.springframework.cloud.service.ServiceConnectorConfig; +import org.springframework.cloud.service.common.RedisServiceInfo; +import org.springframework.cloud.service.keyval.RedisConnectionFactoryFactory; +import org.springframework.data.redis.connection.RedisConnectionFactory; + +/** + * + * @author Ramnivas Laddad + * + */ +public class SpringData1RedisConnectionFactoryFactoryTest extends AbstractCloudServiceConnectorFactoryTest { + @Mock RedisConnectionFactory mockConnector; + + public RedisConnectionFactoryFactory createTestCloudServiceConnectorFactory(String id, ServiceConnectorConfig config) { + return new RedisConnectionFactoryFactory(id, config); + } + + public Class getConnectorType() { + return RedisConnectionFactory.class; + } + + public RedisConnectionFactory getMockConnector() { + return mockConnector; + } + + public RedisServiceInfo getTestServiceInfo(String id) { + return new RedisServiceInfo(id, "host", 0, "password"); + } +} diff --git a/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/redis/SpringData1RedisServiceConnectorCreatorTest.java b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/redis/SpringData1RedisServiceConnectorCreatorTest.java new file mode 100644 index 0000000..3c281ee --- /dev/null +++ b/spring-cloud-spring-service-connector/src/test/java/org/springframework/cloud/service/redis/SpringData1RedisServiceConnectorCreatorTest.java @@ -0,0 +1,60 @@ +package org.springframework.cloud.service.redis; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.cloud.service.common.RedisServiceInfo; +import org.springframework.cloud.service.keyval.SpringData1RedisConnectionFactoryCreator; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.test.util.ReflectionTestUtils; + +/** + * + * @author Ramnivas Laddad + * + */ +public class SpringData1RedisServiceConnectorCreatorTest { + private static final String TEST_HOST = "10.20.30.40"; + private static final int TEST_PORT = 1234; + private static final String TEST_PASSWORD = "mypass"; + + + @Mock private RedisServiceInfo mockRedisServiceInfo; + + private SpringData1RedisConnectionFactoryCreator testCreator = new SpringData1RedisConnectionFactoryCreator(); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void cloudRedisCreationNoConfig() throws Exception { + RedisServiceInfo serviceInfo = createServiceInfo(); + + RedisConnectionFactory dataSource = testCreator.create(serviceInfo, null); + + assertConnectorProperties(serviceInfo, dataSource); + } + + public RedisServiceInfo createServiceInfo() { + when(mockRedisServiceInfo.getHost()).thenReturn(TEST_HOST); + when(mockRedisServiceInfo.getPort()).thenReturn(TEST_PORT); + when(mockRedisServiceInfo.getPassword()).thenReturn(TEST_PASSWORD); + + return mockRedisServiceInfo; + } + + private void assertConnectorProperties(RedisServiceInfo serviceInfo, RedisConnectionFactory connector) { + assertNotNull(connector); + + assertEquals(serviceInfo.getHost(), ReflectionTestUtils.getField(connector, "hostName")); + assertEquals(serviceInfo.getPort(), ReflectionTestUtils.getField(connector, "port")); + assertEquals(serviceInfo.getPassword(), ReflectionTestUtils.getField(connector, "password")); + } +}