From e9f253d34f2c2dddb7e8a3104b6527c91826ec64 Mon Sep 17 00:00:00 2001 From: Mark Pollack Date: Mon, 14 Nov 2011 16:38:18 -0500 Subject: [PATCH] DATAMONGO-316 - Replica Set configuration via properties file throws ArrayIndexOutOfBoundsException --- .../data/mongodb/config/MongoParser.java | 28 ++++++++-- .../data/mongodb/config/ParsingUtils.java | 20 ++----- .../config/ServerAddressPropertyEditor.java | 53 +++++++++++++++++++ .../config/MongoNamespaceReplicaSetTests.java | 35 ++++++++---- .../MongoNamespaceReplicaSetTests-context.xml | 5 ++ .../src/test/resources/replicaSet.properties | 1 + 6 files changed, 112 insertions(+), 30 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java create mode 100644 spring-data-mongodb/src/test/resources/replicaSet.properties diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoParser.java index 96af81427..9e76e5850 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoParser.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoParser.java @@ -16,9 +16,15 @@ package org.springframework.data.mongodb.config; +import java.util.Map; + import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.config.CustomEditorConfigurer; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.data.mongodb.core.MongoFactoryBean; @@ -26,7 +32,7 @@ import org.springframework.util.StringUtils; import org.w3c.dom.Element; /** - * Parser for <mongo;gt; definitions. If no name + * Parser for <mongo;gt; definitions. * * @author Mark Pollack */ @@ -39,17 +45,31 @@ public class MongoParser extends AbstractSingleBeanDefinitionParser { @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - super.doParse(element, builder); ParsingUtils.setPropertyValue(element, builder, "port", "port"); ParsingUtils.setPropertyValue(element, builder, "host", "host"); ParsingUtils.setPropertyValue(element, builder, "write-concern", "writeConcern"); - ParsingUtils.parseMongoOptions(parserContext, element, builder); - ParsingUtils.parseReplicaSet(parserContext, element, builder); + ParsingUtils.parseMongoOptions(element, builder); + ParsingUtils.parseReplicaSet(element, builder); + + registerCustomEditorConfigurer(parserContext.getRegistry()); } + /** + * One should only register one bean definition but want to have the convenience of using AbstractSingleBeanDefinitionParser but have the side effect of + * registering a 'default' property editor with the container. + * @param parserContext the ParserContext to + */ + private void registerCustomEditorConfigurer(BeanDefinitionRegistry registry) { + BeanDefinitionBuilder customEditorConfigurer = BeanDefinitionBuilder.genericBeanDefinition(CustomEditorConfigurer.class); + Map customEditors = new ManagedMap(); + customEditors.put("java.util.List", "org.springframework.data.mongodb.config.ServerAddressPropertyEditor"); + customEditorConfigurer.addPropertyValue("customEditors", customEditors); + BeanDefinitionReaderUtils.registerWithGeneratedName(customEditorConfigurer.getBeanDefinition(), registry); + } + @Override protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ParsingUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ParsingUtils.java index 1f3ef877b..99ced8121 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ParsingUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ParsingUtils.java @@ -19,15 +19,12 @@ package org.springframework.data.mongodb.config; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.data.mongodb.core.MongoOptionsFactoryBean; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; -import com.mongodb.ServerAddress; - abstract class ParsingUtils { /** @@ -38,22 +35,11 @@ abstract class ParsingUtils { * @param mongoBuilder the bean definition builder to populate * @return true if parsing actually occured, false otherwise */ - static boolean parseReplicaSet(ParserContext parserContext, Element element, BeanDefinitionBuilder mongoBuilder) { + static boolean parseReplicaSet(Element element, BeanDefinitionBuilder mongoBuilder) { String replicaSetString = element.getAttribute("replica-set"); if (StringUtils.hasText(replicaSetString)) { - ManagedList serverAddresses = new ManagedList(); - String[] replicaSetStringArray = StringUtils.commaDelimitedListToStringArray(replicaSetString); - for (String element2 : replicaSetStringArray) { - String[] hostAndPort = StringUtils.delimitedListToStringArray(element2, ":"); - BeanDefinitionBuilder defBuilder = BeanDefinitionBuilder.genericBeanDefinition(ServerAddress.class); - defBuilder.addConstructorArgValue(hostAndPort[0]); - defBuilder.addConstructorArgValue(hostAndPort[1]); - serverAddresses.add(defBuilder.getBeanDefinition()); - } - if (!serverAddresses.isEmpty()) { - mongoBuilder.addPropertyValue("replicaSetSeeds", serverAddresses); - } + mongoBuilder.addPropertyValue("replicaSetSeeds", replicaSetString); } return true; @@ -64,7 +50,7 @@ abstract class ParsingUtils { * * @return true if parsing actually occured, false otherwise */ - static boolean parseMongoOptions(ParserContext parserContext, Element element, BeanDefinitionBuilder mongoBuilder) { + static boolean parseMongoOptions(Element element, BeanDefinitionBuilder mongoBuilder) { Element optionsElement = DomUtils.getChildElementByTagName(element, "options"); if (optionsElement == null) { return false; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java new file mode 100644 index 000000000..9b5504fa4 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java @@ -0,0 +1,53 @@ +/* + * 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.mongodb.config; + +import java.beans.PropertyEditorSupport; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.util.StringUtils; + +import com.mongodb.ServerAddress; + +/** + * Parse a string to a List. The format is host1:port1,host2:port2,host3:port3. + * @author Mark Pollack + * + */ +public class ServerAddressPropertyEditor extends PropertyEditorSupport { + + /** + * Parse a string to a List + */ + public void setAsText(String replicaSetString) { + + List serverAddresses = new ArrayList(); + String[] replicaSetStringArray = StringUtils.commaDelimitedListToStringArray(replicaSetString); + for (String element2 : replicaSetStringArray) { + String[] hostAndPort = StringUtils.delimitedListToStringArray(element2, ":"); + try { + serverAddresses.add(new ServerAddress(hostAndPort[0], Integer.parseInt(hostAndPort[1]))); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Could not parse port " + hostAndPort[1], e ); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("Could not parse host " + hostAndPort[0], e ); + } + } + setValue(serverAddresses); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests.java index 53cbe0d65..0affa2f55 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests.java @@ -45,18 +45,35 @@ public class MongoNamespaceReplicaSetTests extends NamespaceTestSupport { public void testParsingMongoWithReplicaSets() throws Exception { assertTrue(ctx.containsBean("replicaSetMongo")); MongoFactoryBean mfb = (MongoFactoryBean) ctx.getBean("&replicaSetMongo"); - + List replicaSetSeeds = readField("replicaSetSeeds", mfb); assertNotNull(replicaSetSeeds); - + assertEquals("127.0.0.1", replicaSetSeeds.get(0).getHost()); assertEquals(10001, replicaSetSeeds.get(0).getPort()); - - assertEquals("localhost", replicaSetSeeds.get(1).getHost()); - assertEquals(10002, replicaSetSeeds.get(1).getPort()); - + + assertEquals("localhost", replicaSetSeeds.get(1).getHost()); + assertEquals(10002, replicaSetSeeds.get(1).getPort()); + } - + + @Test + public void testParsingWithPropertyPlaceHolder() throws Exception { + assertTrue(ctx.containsBean("manyReplicaSetMongo")); + MongoFactoryBean mfb = (MongoFactoryBean) ctx.getBean("&manyReplicaSetMongo"); + + List replicaSetSeeds = readField("replicaSetSeeds", mfb); + assertNotNull(replicaSetSeeds); + + assertEquals("192.168.174.130", replicaSetSeeds.get(0).getHost()); + assertEquals(27017, replicaSetSeeds.get(0).getPort()); + assertEquals("192.168.174.130", replicaSetSeeds.get(1).getHost()); + assertEquals(27018, replicaSetSeeds.get(1).getPort()); + assertEquals("192.168.174.130", replicaSetSeeds.get(2).getHost()); + assertEquals(27019, replicaSetSeeds.get(2).getPort()); + + } + @Test @Ignore("CI infrastructure does not yet support replica sets") public void testMongoWithReplicaSets() { @@ -67,10 +84,10 @@ public class MongoNamespaceReplicaSetTests extends NamespaceTestSupport { assertEquals("localhost", servers.get(1).getHost()); assertEquals(10001, servers.get(0).getPort()); assertEquals(10002, servers.get(1).getPort()); - + MongoTemplate template = new MongoTemplate(mongo, "admin"); CommandResult result = template.executeCommand("{replSetGetStatus : 1}"); assertEquals("blort", result.getString("set")); - + } } diff --git a/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests-context.xml b/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests-context.xml index 5b7dc7df6..dd1890419 100644 --- a/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests-context.xml +++ b/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/config/MongoNamespaceReplicaSetTests-context.xml @@ -7,6 +7,11 @@ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> + + + + +