Add the ability to pass a map of connection properties to the Rabbit service connection creator for more control over the created ConnectionFactory.

This commit is contained in:
Scott Frederick
2015-05-08 14:38:46 -05:00
parent 80c760cf32
commit e2cd8edda5
18 changed files with 227 additions and 79 deletions

View File

@@ -1,8 +1,6 @@
package org.springframework.cloud.config.xml;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.cloud.service.CloudServiceConnectorFactory;
import org.w3c.dom.Node;
/**
* @author Thomas Risberg
@@ -13,8 +11,4 @@ abstract class AbstractNestedElementCloudServiceFactoryParser extends AbstractCl
super(serviceConnectorFactoryType);
}
protected boolean isElement(Node node, ParserContext parserContext, String elementName) {
return node.getNodeType() == Node.ELEMENT_NODE &&
elementName.equals(parserContext.getDelegate().getLocalName(node));
}
}

View File

@@ -5,15 +5,15 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.cloud.service.relational.CloudDataSourceFactory;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Parser for the {@code <cloud:data-source>} namespace element
*
* @author Ramnivas Laddad
* @author Thomas Risberg
* @author Scott Frederick
*/
public class CloudDataSourceFactoryParser extends AbstractPoolingCloudServiceFactoryParser {
@@ -27,16 +27,17 @@ public class CloudDataSourceFactoryParser extends AbstractPoolingCloudServiceFac
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
super.doParse(element, parserContext, builder);
BeanDefinition cloudConnectionConfiguration = null;
Element connectionElement = DomUtils.getChildElementByTagName(element, ELEMENT_CONNECTION);
if (connectionElement != null) {
cloudConnectionConfiguration = parseConnectionElement(connectionElement);
}
BeanDefinition cloudPoolConfiguration = null;
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (isElement(child, parserContext, ELEMENT_CONNECTION)) {
cloudConnectionConfiguration = parseConnectionElement((Element) child);
} else if (isElement(child, parserContext, ELEMENT_POOL)) {
cloudPoolConfiguration = parsePoolElement((Element) child, parserContext);
}
Element poolElement = DomUtils.getChildElementByTagName(element, ELEMENT_POOL);
if (poolElement != null) {
cloudPoolConfiguration = parsePoolElement(poolElement, parserContext);
}
BeanDefinitionBuilder dataSourceConfigBeanBuilder =

View File

@@ -7,15 +7,15 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.cloud.service.document.MongoDbFactoryFactory;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Parser for the {@code <cloud:mongo-db-factory>} namespace element
*
* @author Thomas Risberg
* @author Ramnivas Laddad
* @author Scott Frederick
*/
public class CloudMongoDbFactoryParser extends AbstractNestedElementCloudServiceFactoryParser {
@@ -34,7 +34,7 @@ public class CloudMongoDbFactoryParser extends AbstractNestedElementCloudService
Map<String, String> attributeMap = new HashMap<String, String>();
parseWriteConcern(element, attributeMap);
parseMongoOptionsElement(element, parserContext, attributeMap);
parseMongoOptionsElement(element, attributeMap);
BeanDefinitionBuilder cloudMongoConfigurationBeanBuilder =
BeanDefinitionBuilder.genericBeanDefinition("org.springframework.cloud.service.document.MongoDbFactoryConfig");
@@ -53,21 +53,16 @@ public class CloudMongoDbFactoryParser extends AbstractNestedElementCloudService
}
}
private void parseMongoOptionsElement(Element element, ParserContext parserContext, Map<String, String> attributeMap) {
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (isElement(child, parserContext, ELEMENT_MONGO_OPTIONS)) {
Element optionElement = (Element) child;
String connectionsPerHost = optionElement.getAttribute(CONNECTIONS_PER_HOST);
if (StringUtils.hasText(connectionsPerHost)) {
attributeMap.put(CONNECTIONS_PER_HOST, connectionsPerHost);
}
String maxWaitTime = optionElement.getAttribute(MAX_WAIT_TIME);
if (StringUtils.hasText(maxWaitTime)) {
attributeMap.put(MAX_WAIT_TIME, maxWaitTime);
}
private void parseMongoOptionsElement(Element element, Map<String, String> attributeMap) {
Element optionsElement = DomUtils.getChildElementByTagName(element, ELEMENT_MONGO_OPTIONS);
if (optionsElement != null) {
String connectionsPerHost = optionsElement.getAttribute(CONNECTIONS_PER_HOST);
if (StringUtils.hasText(connectionsPerHost)) {
attributeMap.put(CONNECTIONS_PER_HOST, connectionsPerHost);
}
String maxWaitTime = optionsElement.getAttribute(MAX_WAIT_TIME);
if (StringUtils.hasText(maxWaitTime)) {
attributeMap.put(MAX_WAIT_TIME, maxWaitTime);
}
}
}

View File

@@ -26,6 +26,8 @@ public class CloudNamespaceHandler extends NamespaceHandlerSupport {
registerBeanDefinitionParser("mongo-db-factory", new CloudMongoDbFactoryParser());
registerBeanDefinitionParser("data-source", new CloudDataSourceFactoryParser());
registerBeanDefinitionParser("connection-properties", new ConnectionPropertiesParser());
this.registerBeanDefinitionParser("properties", new AbstractSimpleBeanDefinitionParser() {
@Override
protected Class<?> getBeanClass(Element element) {

View File

@@ -4,19 +4,23 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.cloud.service.messaging.RabbitConnectionFactoryFactory;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.Map;
/**
* Parser for the {@code <cloud:rabbit-connection-factory>} namespace element
*
* @author Thomas Risberg
* @author Ramnivas Laddad
* @author Scott Frederick
*/
public class CloudRabbitConnectionFactoryParser extends AbstractNestedElementCloudServiceFactoryParser {
private static final String ELEMENT_RABBIT_OPTIONS = "rabbit-options";
private static final String RABBIT_OPTIONS = "rabbit-options";
private static final String CONNECTION_PROPERTIES = "connection-properties";
private static final String CHANNEL_CACHE_SIZE = "channel-cache-size";
public CloudRabbitConnectionFactoryParser() {
super(RabbitConnectionFactoryFactory.class);
@@ -25,25 +29,29 @@ public class CloudRabbitConnectionFactoryParser extends AbstractNestedElementClo
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
super.doParse(element, parserContext, builder);
BeanDefinition cloudRabbitConfiguration = null;
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (isElement(child, parserContext, ELEMENT_RABBIT_OPTIONS)) {
cloudRabbitConfiguration = parseRabbitOptionsElement((Element) child);
}
Element rabbitOptionsElement = DomUtils.getChildElementByTagName(element, RABBIT_OPTIONS);
if (rabbitOptionsElement != null) {
cloudRabbitConfiguration = parseRabbitOptionsElement(rabbitOptionsElement, parserContext);
}
builder.addConstructorArgValue(cloudRabbitConfiguration);
}
private BeanDefinition parseRabbitOptionsElement(Element element) {
BeanDefinitionBuilder cloudRabbitConfigurationBeanBuilder =
private BeanDefinition parseRabbitOptionsElement(Element element, ParserContext parserContext) {
BeanDefinitionBuilder configBeanBuilder =
BeanDefinitionBuilder.genericBeanDefinition("org.springframework.cloud.service.messaging.RabbitConnectionFactoryConfig");
String channelCacheSize = element.getAttribute("channel-cache-size");
cloudRabbitConfigurationBeanBuilder.addConstructorArgValue(channelCacheSize);
Element propertiesElement = DomUtils.getChildElementByTagName(element, CONNECTION_PROPERTIES);
if (propertiesElement != null) {
Map<?, ?> map = parserContext.getDelegate().parseMapElement(propertiesElement, configBeanBuilder.getRawBeanDefinition());
configBeanBuilder.addConstructorArgValue(map);
}
return cloudRabbitConfigurationBeanBuilder.getBeanDefinition();
String channelCacheSize = element.getAttribute(CHANNEL_CACHE_SIZE);
configBeanBuilder.addConstructorArgValue(channelCacheSize);
return configBeanBuilder.getBeanDefinition();
}
}

View File

@@ -4,34 +4,32 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.cloud.service.keyval.RedisConnectionFactoryFactory;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Parser for the {@code <cloud:redis-connection-factory>} namespace element
*
* @author Ramnivas Laddad
* @author Scott Frederick
*
*/
public class CloudRedisConnectionFactoryParser extends AbstractPoolingCloudServiceFactoryParser {
private static final String ELEMENT_POOL = "pool";
public CloudRedisConnectionFactoryParser() {
super(RedisConnectionFactoryFactory.class);
}
private static final String ELEMENT_POOL = "pool";
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
super.doParse(element, parserContext, builder);
BeanDefinition cloudPoolConfiguration = null;
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (isElement(child, parserContext, ELEMENT_POOL)) {
cloudPoolConfiguration = parsePoolElement((Element) child, parserContext);
}
Element poolElement = DomUtils.getChildElementByTagName(element, ELEMENT_POOL);
if (poolElement != null) {
cloudPoolConfiguration = parsePoolElement(poolElement, parserContext);
}
BeanDefinitionBuilder redisConfigBeanBuilder =

View File

@@ -0,0 +1,25 @@
package org.springframework.cloud.config.xml;
import org.springframework.beans.factory.config.MapFactoryBean;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import java.util.Map;
/**
* @author Scott Frederick
*/
class ConnectionPropertiesParser extends AbstractSingleBeanDefinitionParser {
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
Map<?, ?> map = parserContext.getDelegate().parseMapElement(element, builder.getRawBeanDefinition());
builder.addPropertyValue("sourceMap", map);
}
@Override
protected String getBeanClassName(Element element) {
return MapFactoryBean.class.getName();
}
}

View File

@@ -0,0 +1,29 @@
package org.springframework.cloud.service;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import java.util.Map;
/**
* Configurer for service connector that takes a map of properties to apply to the connection.
*
* @author Scott Frederick
*
* @param <SC> service connector type
* @param <SCC> service connector configurer type
*/
public class MapServiceConnectionConfigurer<SC, SCC extends MapServiceConnectorConfig>
implements ServiceConnectorConfigurer<SC, SCC> {
@Override
public SC configure(SC serviceConnector, SCC config) {
if (config != null) {
Map<String, Object> properties = config.getConnectionProperties();
if (properties != null) {
BeanWrapper target = new BeanWrapperImpl(serviceConnector);
target.setPropertyValues(properties);
}
}
return serviceConnector;
}
}

View File

@@ -0,0 +1,20 @@
package org.springframework.cloud.service;
import java.util.Map;
/**
* Service connector configuration that takes a map of properties to apply to the connection.
*
* @author Scott Frederick
*/
public class MapServiceConnectorConfig implements ServiceConnectorConfig {
private Map<String, Object> properties;
public MapServiceConnectorConfig(Map<String, Object> properties) {
this.properties = properties;
}
public Map<String, Object> getConnectionProperties() {
return properties;
}
}

View File

@@ -1,21 +1,33 @@
package org.springframework.cloud.service.messaging;
import org.springframework.cloud.service.ServiceConnectorConfig;
import org.springframework.cloud.service.MapServiceConnectorConfig;
import java.util.Map;
/**
* Class to hold configuration values for a Rabbit
* Class to hold configuration values for a Rabbit connection
*
* @author Thomas Risberg
* @author Ramnivas Laddad
* @author Scott Frederick
*/
public class RabbitConnectionFactoryConfig implements ServiceConnectorConfig {
public class RabbitConnectionFactoryConfig extends MapServiceConnectorConfig {
private Integer channelCacheSize;
public RabbitConnectionFactoryConfig(Map<String, Object> properties) {
super(properties);
}
public RabbitConnectionFactoryConfig(Integer channelCacheSize) {
super(null);
this.channelCacheSize = channelCacheSize;
}
public RabbitConnectionFactoryConfig(Map<String, Object> properties, Integer channelCacheSize) {
super(properties);
this.channelCacheSize = channelCacheSize;
}
public Integer getChannelCacheSize() {
return channelCacheSize;
}

View File

@@ -0,0 +1,11 @@
package org.springframework.cloud.service.messaging;
import com.rabbitmq.client.ConnectionFactory;
import org.springframework.cloud.service.MapServiceConnectionConfigurer;
import org.springframework.cloud.service.MapServiceConnectorConfig;
/**
* @author Scott Frederick
*/
public class RabbitConnectionFactoryConfigurer extends MapServiceConnectionConfigurer<ConnectionFactory, MapServiceConnectorConfig> {
}

View File

@@ -13,8 +13,10 @@ import org.springframework.cloud.service.common.AmqpServiceInfo;
* @author Dave Syer
* @author Thomas Risberg
* @author Mark Fisher
* @author Scott Frederick
*/
public class RabbitConnectionFactoryCreator extends AbstractServiceConnectorCreator<ConnectionFactory, AmqpServiceInfo> {
private RabbitConnectionFactoryConfigurer configurer = new RabbitConnectionFactoryConfigurer();
@Override
public ConnectionFactory create(AmqpServiceInfo serviceInfo, ServiceConnectorConfig serviceConnectorConfiguration) {
@@ -23,12 +25,16 @@ public class RabbitConnectionFactoryCreator extends AbstractServiceConnectorCrea
connectionFactory.setUri(serviceInfo.getUri());
}
catch (Exception e) {
throw new IllegalArgumentException("failed to create ConnectionFactory", e);
throw new IllegalArgumentException("Failed to create ConnectionFactory", e);
}
configurer.configure(connectionFactory, (RabbitConnectionFactoryConfig) serviceConnectorConfiguration);
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(connectionFactory);
if (serviceConnectorConfiguration != null) {
cachingConnectionFactory.setChannelCacheSize(((RabbitConnectionFactoryConfig)serviceConnectorConfiguration).getChannelCacheSize());
}
return cachingConnectionFactory;
}

View File

@@ -5,7 +5,7 @@ import org.springframework.cloud.service.AbstractCloudServiceConnectorFactory;
import org.springframework.cloud.service.ServiceConnectorConfig;
/**
* Spring factory bean for redis service.
* Spring factory bean for RabbitMQ service.
*
* @author Ramnivas Laddad
*

View File

@@ -282,13 +282,43 @@
Element defining optional Rabbit configuration settings.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element ref="connection-properties" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="channel-cache-size" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The size of the channel cache.
]]></xsd:documentation>
The size of the channel cache.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="connection-properties" type="mapType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Additional connection configuration properties.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:complexType name="mapType">
<xsd:complexContent>
<xsd:extension base="beans:mapType">
<xsd:attribute name="ref" use="optional">
<xsd:annotation>
<xsd:documentation source="java:java.util.Map"><![CDATA[
The bean name of the Map to pass as configuration options.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="java.util.Map" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:schema>

View File

@@ -9,13 +9,20 @@ import org.springframework.test.util.ReflectionTestUtils;
/**
*
* @author Ramnivas Laddad
* @author Scott Frederick
*
*/
public class RabbitConnectionFactoryCloudConfigTestHelper {
public static void assertConfigProperties(ConnectionFactory connector, Integer channelCacheSize) {
public static void assertConfigProperties(ConnectionFactory connector, Integer channelCacheSize,
Integer requestedHeartbeat, Integer connectionTimeout) {
assertNotNull(connector);
assertEquals(channelCacheSize, ReflectionTestUtils.getField(connector, "channelCacheSize"));
Object rabbitConnectionFactory = ReflectionTestUtils.getField(connector, "rabbitConnectionFactory");
assertNotNull(rabbitConnectionFactory);
assertEquals(requestedHeartbeat, ReflectionTestUtils.getField(rabbitConnectionFactory, "requestedHeartbeat"));
assertEquals(connectionTimeout, ReflectionTestUtils.getField(rabbitConnectionFactory, "connectionTimeout"));
}
}

View File

@@ -8,9 +8,13 @@ import org.springframework.cloud.service.messaging.RabbitConnectionFactoryConfig
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author Ramnivas Laddad
* @author Scott Frederick
*
*/
public class RabbitConnectionFactoryJavaConfigTest extends AbstractServiceJavaConfigTest<ConnectionFactory> {
@@ -27,12 +31,12 @@ public class RabbitConnectionFactoryJavaConfigTest extends AbstractServiceJavaCo
}
@Test
public void cloudRabbitConnectionFactoryWithChannelCacheSize() {
public void cloudRabbitConnectionFactoryWithConfig() {
ApplicationContext testContext =
getTestApplicationContext(RabbitConnectionFactoryConfigWithServiceConfig.class, createService("my-service"));
ConnectionFactory connector = testContext.getBean("channelCacheSize200", getConnectorType());
RabbitConnectionFactoryCloudConfigTestHelper.assertConfigProperties(connector, 200);
ConnectionFactory connector = testContext.getBean("connectionFactoryWithConfig", getConnectorType());
RabbitConnectionFactoryCloudConfigTestHelper.assertConfigProperties(connector, 200, 5, 10);
}
}
@@ -52,8 +56,11 @@ class RabbitConnectionFactoryConfigWithoutId extends AbstractCloudConfig {
class RabbitConnectionFactoryConfigWithServiceConfig extends AbstractCloudConfig {
@Bean
public ConnectionFactory channelCacheSize200() {
RabbitConnectionFactoryConfig serviceConfig = new RabbitConnectionFactoryConfig(200);
return connectionFactory().rabbitConnectionFactory("my-service", serviceConfig);
public ConnectionFactory connectionFactoryWithConfig() {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("requestedHeartbeat", 5);
properties.put("connectionTimeout", 10);
RabbitConnectionFactoryConfig serviceConfig = new RabbitConnectionFactoryConfig(properties, 200);
return connectionFactory().rabbitConnectionFactory("my-service", serviceConfig);
}
}

View File

@@ -30,11 +30,11 @@ public class RabbitConnectionFactoryXmlConfigTest extends AbstractServiceXmlConf
}
@Test
public void cloudRabbitConnectionFactoryWithChannelCacheSize() {
public void cloudRabbitConnectionFactoryWithConfiguration() {
ApplicationContext testContext = getTestApplicationContext("cloud-rabbit-with-config.xml", createService("my-service"));
ConnectionFactory connector = testContext.getBean("service-channelCacheSize200", getConnectorType());
RabbitConnectionFactoryCloudConfigTestHelper.assertConfigProperties(connector, 200);
RabbitConnectionFactoryCloudConfigTestHelper.assertConfigProperties(connector, 200, 5, 10);
}
}

View File

@@ -4,10 +4,13 @@
xmlns:cloud="http://www.springframework.org/schema/cloud"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cloud http://www.springframework.org/schema/cloud/spring-cloud.xsd">
<cloud:rabbit-connection-factory id="service-channelCacheSize200" service-name="my-service">
<cloud:rabbit-options channel-cache-size="200"/>
<cloud:rabbit-options channel-cache-size="200">
<cloud:connection-properties>
<entry key="requestedHeartbeat" value="5"/>
<entry key="connectionTimeout" value="10"/>
</cloud:connection-properties>
</cloud:rabbit-options>
</cloud:rabbit-connection-factory>
</beans>