diff --git a/spring-cassandra/pom.xml b/spring-cassandra/pom.xml index b3afd4fd1..666c35b88 100644 --- a/spring-cassandra/pom.xml +++ b/spring-cassandra/pom.xml @@ -41,6 +41,11 @@ org.springframework spring-tx + + ${project.groupId} + spring-data-commons + ${springdata.commons} + com.datastax.cassandra cassandra-driver-core diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/CassandraClusterFactoryBean.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/CassandraClusterFactoryBean.java index cdd63f6c4..5c7610159 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/config/CassandraClusterFactoryBean.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/CassandraClusterFactoryBean.java @@ -16,8 +16,10 @@ package org.springframework.cassandra.config; import java.util.ArrayList; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,7 +31,7 @@ import org.springframework.cassandra.core.cql.generator.CreateKeyspaceCqlGenerat import org.springframework.cassandra.core.cql.generator.DropKeyspaceCqlGenerator; import org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification; import org.springframework.cassandra.core.keyspace.DropKeyspaceSpecification; -import org.springframework.cassandra.core.keyspace.KeyspaceNameSpecification; +import org.springframework.cassandra.core.keyspace.KeyspaceActionSpecification; import org.springframework.cassandra.support.CassandraExceptionTranslator; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; @@ -51,6 +53,7 @@ import com.datastax.driver.core.policies.RetryPolicy; * * @author Alex Shvid * @author Matthew T. Adams + * @author David Webb */ public class CassandraClusterFactoryBean implements FactoryBean, InitializingBean, DisposableBean, PersistenceExceptionTranslator { @@ -62,9 +65,10 @@ public class CassandraClusterFactoryBean implements FactoryBean, Initia protected static final Logger log = LoggerFactory.getLogger(CassandraClusterFactoryBean.class); private Cluster cluster; + private boolean accumulating = true; - /** - * Comma-delimited string of servers. + /* + * Attributes needed for cluster builder */ private String contactPoints = DEFAULT_CONTACT_POINTS; private int port = CassandraClusterFactoryBean.DEFAULT_PORT; @@ -77,6 +81,7 @@ public class CassandraClusterFactoryBean implements FactoryBean, Initia private ReconnectionPolicy reconnectionPolicy; private RetryPolicy retryPolicy; private boolean metricsEnabled = DEFAULT_METRICS_ENABLED; + private Set> keyspaceSpecifications = new HashSet>(); private List keyspaceCreations = new ArrayList(); private List keyspaceDrops = new ArrayList(); private List startupScripts = new ArrayList(); @@ -152,9 +157,31 @@ public class CassandraClusterFactoryBean implements FactoryBean, Initia } cluster = builder.build(); + + generateSpecificationsFromFactoryBeans(); + executeSpecsAndScripts(keyspaceCreations, startupScripts); } + /** + * Examines the contents of all the KeyspaceSpecificationFactoryBeans and generates the proper KeyspaceSpecification + * from them. + */ + private void generateSpecificationsFromFactoryBeans() { + + for (KeyspaceActionSpecification spec : keyspaceSpecifications) { + + if (spec instanceof CreateKeyspaceSpecification) { + keyspaceCreations.add((CreateKeyspaceSpecification) spec); + } + if (spec instanceof DropKeyspaceSpecification) { + keyspaceDrops.add((DropKeyspaceSpecification) spec); + } + + } + + } + protected void executeSpecsAndScripts(@SuppressWarnings("rawtypes") List specs, List scripts) { Session system = null; @@ -167,7 +194,7 @@ public class CassandraClusterFactoryBean implements FactoryBean, Initia Iterator i = specs.iterator(); while (i.hasNext()) { - KeyspaceNameSpecification spec = (KeyspaceNameSpecification) i.next(); + KeyspaceActionSpecification spec = (KeyspaceActionSpecification) i.next(); String cql = (spec instanceof CreateKeyspaceSpecification) ? new CreateKeyspaceCqlGenerator( (CreateKeyspaceSpecification) spec).toCql() : new DropKeyspaceCqlGenerator( (DropKeyspaceSpecification) spec).toCql(); @@ -343,4 +370,35 @@ public class CassandraClusterFactoryBean implements FactoryBean, Initia return socketOptions; } + + /** + * @return Returns the keyspaceSpecifications. + */ + public Set> getKeyspaceSpecifications() { + return keyspaceSpecifications; + } + + /** + * If accumlating is true, we append to the list, otherwise we replace the list. + * + * @param keyspaceSpecifications The keyspaceSpecifications to set. + */ + public void setKeyspaceSpecifications(Set> keyspaceSpecifications) { + log.info("Setter Called"); + this.keyspaceSpecifications = keyspaceSpecifications; + } + + /** + * @return Returns the accumulating. + */ + public boolean isAccumulating() { + return accumulating; + } + + /** + * @param accumulating The accumulating to set. + */ + public void setAccumulating(boolean accumulating) { + this.accumulating = accumulating; + } } diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/KeyspaceAction.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/KeyspaceAction.java new file mode 100644 index 000000000..51f6e93c6 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/KeyspaceAction.java @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2012 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.cassandra.config; + +/** + * Available actions for Keyspace Specifications + * + * @author David Webb + */ +public enum KeyspaceAction { + CREATE, CREATE_DROP, ALTER; +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/KeyspaceActionSpecificationFactoryBean.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/KeyspaceActionSpecificationFactoryBean.java new file mode 100644 index 000000000..5fb25d3fe --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/KeyspaceActionSpecificationFactoryBean.java @@ -0,0 +1,185 @@ +/* + * Copyright 2011-2014 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.cassandra.config; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification; +import org.springframework.cassandra.core.keyspace.DefaultOption; +import org.springframework.cassandra.core.keyspace.DropKeyspaceSpecification; +import org.springframework.cassandra.core.keyspace.KeyspaceActionSpecification; +import org.springframework.cassandra.core.keyspace.KeyspaceOption; +import org.springframework.cassandra.core.keyspace.Option; +import org.springframework.util.Assert; + +/** + * @author David Webb (dwebb@brightmove.com) + * + */ +public class KeyspaceActionSpecificationFactoryBean implements FactoryBean>>, + InitializingBean, DisposableBean { + + private final static Logger log = LoggerFactory.getLogger(KeyspaceActionSpecificationFactoryBean.class); + + private KeyspaceAction action; + private String name; + private Map replicationOptions = new LinkedHashMap(); + private boolean durableWrites = false; + private boolean ifNotExists = false; + + private Set> specs = new HashSet>(); + + @Override + public void destroy() throws Exception { + name = null; + replicationOptions = null; + specs = null; + } + + @Override + public void afterPropertiesSet() throws Exception { + + Assert.hasText(name, "Keyspace Name is required for a Keyspace Action"); + Assert.notNull(action, "Keyspace Action is required for a Keyspace Action"); + + switch (action) { + case CREATE_DROP: + specs.add(generateDropKeyspaceSpecification()); + case CREATE: + specs.add(generateCreateKeyspaceSpecification()); + break; + case ALTER: + break; + } + + } + + private CreateKeyspaceSpecification generateCreateKeyspaceSpecification() { + CreateKeyspaceSpecification create = new CreateKeyspaceSpecification(); + create.name(name).ifNotExists(ifNotExists).with(KeyspaceOption.DURABLE_WRITES, durableWrites); + if (replicationOptions != null && replicationOptions.size() > 0) { + create.with(KeyspaceOption.REPLICATION, replicationOptions); + } else { + Map defaultReplicationStrategyMap = new HashMap(); + defaultReplicationStrategyMap.put(new DefaultOption("class", String.class, true, false, true), + KeyspaceOption.ReplicationStrategy.SIMPLE_STRATEGY); + defaultReplicationStrategyMap.put(new DefaultOption("replication_factor", String.class, true, false, false), "1"); + create.with(KeyspaceOption.REPLICATION, defaultReplicationStrategyMap); + } + return create; + } + + private DropKeyspaceSpecification generateDropKeyspaceSpecification() { + DropKeyspaceSpecification drop = new DropKeyspaceSpecification(); + drop.name(getName()); + return drop; + } + + @Override + public Set> getObject() throws Exception { + return specs; + } + + @Override + public Class getObjectType() { + return Set.class; + } + + @Override + public boolean isSingleton() { + return false; + } + + /** + * @return Returns the name. + */ + public String getName() { + return name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return Returns the ifNotExists. + */ + public boolean isIfNotExists() { + return ifNotExists; + } + + /** + * @param ifNotExists The ifNotExists to set. + */ + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + /** + * @return Returns the action. + */ + public KeyspaceAction getAction() { + return action; + } + + /** + * @param action The action to set. + */ + public void setAction(KeyspaceAction action) { + this.action = action; + } + + /** + * @return Returns the replicationOptions. + */ + public Map getReplicationOptions() { + return replicationOptions; + } + + /** + * @param replicationOptions The replicationOptions to set. + */ + public void setReplicationOptions(Map replicationOptions) { + this.replicationOptions = replicationOptions; + } + + /** + * @return Returns the durableWrites. + */ + public boolean isDurableWrites() { + return durableWrites; + } + + /** + * @param durableWrites The durableWrites to set. + */ + public void setDurableWrites(boolean durableWrites) { + this.durableWrites = durableWrites; + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/MultiLevelListFlattenerFactoryBean.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/MultiLevelListFlattenerFactoryBean.java new file mode 100644 index 000000000..baf973256 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/MultiLevelListFlattenerFactoryBean.java @@ -0,0 +1,71 @@ +/* + * Copyright 2011-2014 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.cassandra.config; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.FactoryBean; + +/** + * Given List of Lists where all child Lists contain the same class, then a single level List of is generated. + * + * @author David Webb + * @param + * + */ +public class MultiLevelListFlattenerFactoryBean implements FactoryBean> { + + private List> multiLevelList; + + @Override + public List getObject() throws Exception { + List list = new ArrayList(); + + for (List topList : multiLevelList) { + for (T t : topList) { + list.add(t); + } + } + + return list; + } + + @Override + public Class getObjectType() { + return List.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + /** + * @return Returns the multiLevelList. + */ + public List> getMultiLevelList() { + return multiLevelList; + } + + /** + * @param multiLevelList The multiLevelList to set. + */ + public void setMultiLevelList(List> multiLevelList) { + this.multiLevelList = multiLevelList; + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/MultiLevelSetFlattenerFactoryBean.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/MultiLevelSetFlattenerFactoryBean.java new file mode 100644 index 000000000..7692c7023 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/MultiLevelSetFlattenerFactoryBean.java @@ -0,0 +1,77 @@ +/* + * Copyright 2011-2014 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.cassandra.config; + +import java.util.HashSet; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.FactoryBean; + +/** + * Given Set of Sets where all child Sets contain the same class, then a single level Set of is generated. + * + * @author David Webb + * @param + * + */ +public class MultiLevelSetFlattenerFactoryBean implements FactoryBean> { + + private final static Logger log = LoggerFactory.getLogger(MultiLevelSetFlattenerFactoryBean.class); + + private Set> multiLevelSet; + + @Override + public Set getObject() throws Exception { + Set set = new HashSet(); + + for (Set topSet : multiLevelSet) { + for (T t : topSet) { + log.info(t.toString()); + log.info("Set contains -> " + set.contains(t)); + set.add(t); + } + } + + return set; + } + + @Override + public Class getObjectType() { + return Set.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + /** + * @return Returns the multiLevelSet. + */ + public Set> getMultiLevelSet() { + return multiLevelSet; + } + + /** + * @param multiLevelSet The multiLevelSet to set. + */ + public void setMultiLevelSet(Set> multiLevelSet) { + this.multiLevelSet = multiLevelSet; + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/java/AbstractCassandraConfiguration.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/java/AbstractCassandraConfiguration.java index b9f3bb682..9ec7b70e1 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/config/java/AbstractCassandraConfiguration.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/java/AbstractCassandraConfiguration.java @@ -8,8 +8,6 @@ import org.springframework.cassandra.config.CassandraSessionFactoryBean; import org.springframework.cassandra.config.CompressionType; import org.springframework.cassandra.config.PoolingOptionsConfig; import org.springframework.cassandra.config.SocketOptionsConfig; -import org.springframework.cassandra.core.CassandraOperations; -import org.springframework.cassandra.core.CassandraTemplate; import org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification; import org.springframework.cassandra.core.keyspace.DropKeyspaceSpecification; import org.springframework.context.annotation.Bean; diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraClusterParser.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraClusterParser.java index b931fb325..231aa1e3c 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraClusterParser.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraClusterParser.java @@ -15,44 +15,50 @@ */ package org.springframework.cassandra.config.xml; +import static org.springframework.data.config.ParsingUtils.getSourceBeanDefinition; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanDefinitionStoreException; 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.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.beans.factory.support.ManagedSet; +import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.cassandra.config.CassandraClusterFactoryBean; -import org.springframework.cassandra.config.CompressionType; +import org.springframework.cassandra.config.KeyspaceActionSpecificationFactoryBean; import org.springframework.cassandra.config.KeyspaceAttributes; +import org.springframework.cassandra.config.MultiLevelSetFlattenerFactoryBean; import org.springframework.cassandra.config.PoolingOptionsConfig; import org.springframework.cassandra.config.SocketOptionsConfig; -import org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification; import org.springframework.cassandra.core.keyspace.DefaultOption; -import org.springframework.cassandra.core.keyspace.DropKeyspaceSpecification; -import org.springframework.cassandra.core.keyspace.KeyspaceOption; +import org.springframework.cassandra.core.keyspace.KeyspaceOption.ReplicationStrategy; import org.springframework.cassandra.core.keyspace.Option; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; -import org.w3c.dom.NodeList; + +import com.datastax.driver.core.AuthProvider; /** - * Parser for <cluster;gt; definitions. - * * @author Alex Shvid * @author Matthew T. Adams + * @author David Webb */ -public class CassandraClusterParser extends AbstractSimpleBeanDefinitionParser { +public class CassandraClusterParser extends AbstractBeanDefinitionParser { - @Override - protected Class getBeanClass(Element element) { - return CassandraClusterFactoryBean.class; - } + private final static Logger log = LoggerFactory.getLogger(CassandraClusterParser.class); + + // @Override + // protected Class getBeanClass(Element element) { + // return CassandraClusterFactoryBean.class; + // } @Override protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) @@ -63,7 +69,27 @@ public class CassandraClusterParser extends AbstractSimpleBeanDefinitionParser { } @Override - protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { + + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CassandraClusterFactoryBean.class); + builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); + builder.getRawBeanDefinition().setDestroyMethodName("destroy"); + if (parserContext.isNested()) { + // Inner bean definition must receive same scope as containing bean. + builder.setScope(parserContext.getContainingBeanDefinition().getScope()); + } + + if (parserContext.isDefaultLazyInit()) { + // Default-lazy-init applies to custom bean definitions as well. + builder.setLazyInit(true); + } + + doParse(element, parserContext, builder); + + return builder.getBeanDefinition(); + } + + protected void doParse(Element element, ParserContext context, BeanDefinitionBuilder builder) { String contactPoints = element.getAttribute("contactPoints"); if (StringUtils.hasText(contactPoints)) { @@ -77,23 +103,30 @@ public class CassandraClusterParser extends AbstractSimpleBeanDefinitionParser { String compression = element.getAttribute("compression"); if (StringUtils.hasText(compression)) { - builder.addPropertyValue("compressionType", CompressionType.valueOf(compression)); + builder.addPropertyValue("compressionType", compression); } - parseChildElements(builder, element); + String authProvider = element.getAttribute("auth-info-provider-ref"); + if (StringUtils.hasText(authProvider)) { + log.info(authProvider); + builder.addPropertyReference("authProvider", authProvider); + } + + parseChildElements(element, context, builder); } - protected void parseChildElements(BeanDefinitionBuilder builder, Element element) { + protected void parseChildElements(Element element, ParserContext context, BeanDefinitionBuilder builder) { - List creates = new ArrayList(); - List drops = new ArrayList(); + ManagedSet keyspaceActionSpecificationBeanDefinitions = new ManagedSet(); List startupScripts = new ArrayList(); List shutdownScripts = new ArrayList(); List elements = DomUtils.getChildElements(element); + BeanDefinition keyspaceActionSpecificationBeanDefinition = null; // parse nested elements for (Element subElement : elements) { + String name = subElement.getLocalName(); if ("local-pooling-options".equals(name)) { @@ -104,14 +137,9 @@ public class CassandraClusterParser extends AbstractSimpleBeanDefinitionParser { builder.addPropertyValue("socketOptions", parseSocketOptions(subElement)); } else if ("keyspace".equals(name)) { - KeyspaceSpecifications specifications = parseKeyspace(subElement); + keyspaceActionSpecificationBeanDefinition = getKeyspaceSpecificationBeanDefinition(subElement, context); + keyspaceActionSpecificationBeanDefinitions.add(keyspaceActionSpecificationBeanDefinition); - if (specifications.create != null) { - creates.add(specifications.create); - } - if (specifications.drop != null) { - drops.add(specifications.drop); - } } else if ("startup-cql".equals(name)) { startupScripts.add(parseScript(subElement)); } else if ("shutdown-cql".equals(name)) { @@ -119,83 +147,80 @@ public class CassandraClusterParser extends AbstractSimpleBeanDefinitionParser { } } - builder.addPropertyValue("keyspaceCreations", creates); - builder.addPropertyValue("keyspaceDrops", drops); + builder.addPropertyValue("keyspaceSpecifications", + getKeyspaceSetFlattenerBeanDefinition(element, context, keyspaceActionSpecificationBeanDefinitions)); builder.addPropertyValue("startupScripts", startupScripts); builder.addPropertyValue("shutdownScripts", startupScripts); } - protected KeyspaceSpecifications parseKeyspace(Element element) { + /** + * Create the Single Factory Bean that will flatten all List> + * + * @param element + * @param context + * @param keyspaceActionSpecificationBeanDefinitions + * @return + */ + private Object getKeyspaceSetFlattenerBeanDefinition(Element element, ParserContext context, + ManagedSet keyspaceActionSpecificationBeanDefinitions) { - CreateKeyspaceSpecification create = null; - DropKeyspaceSpecification drop = null; + BeanDefinitionBuilder flat = BeanDefinitionBuilder.genericBeanDefinition(MultiLevelSetFlattenerFactoryBean.class); + flat.addPropertyValue("multiLevelSet", keyspaceActionSpecificationBeanDefinitions); + return getSourceBeanDefinition(flat, context, element); - String name = element.getAttribute("name"); - if (name == null || name.trim().length() == 0) { - name = BeanNames.CASSANDRA_KEYSPACE; - } - - boolean durableWrites = Boolean.valueOf(element.getAttribute("durable-writes")); - - String action = element.getAttribute("action"); - if (action == null || action.trim().length() == 0) { - throw new IllegalArgumentException("attribute action must be given"); - } - - if (action.startsWith("CREATE")) { - - create = CreateKeyspaceSpecification.createKeyspace().name(name) - .with(KeyspaceOption.DURABLE_WRITES, durableWrites); - - NodeList nodes = element.getElementsByTagName("replication"); - create = parseReplication((Element) (nodes.getLength() == 1 ? nodes.item(0) : null), create); - } - - if (action.equals("CREATE-DROP")) { - drop = DropKeyspaceSpecification.dropKeyspace().name(create.getName()); - } - - return new KeyspaceSpecifications(create, drop); } - protected CreateKeyspaceSpecification parseReplication(Element element, CreateKeyspaceSpecification create) { + /** + * Parses the keyspace replication options and adds them to the supplied BeanDefinitionBuilder. + * + * @param element + * @param builder + */ + /** + * @param element + * @param builder + */ + protected void parseReplication(Element element, BeanDefinitionBuilder builder) { - String strategyClass = null; - if (element != null) { - strategyClass = element.getAttribute("class"); + if (element == null) { + return; } - if (strategyClass == null || (strategyClass = strategyClass.trim()).length() == 0) { + + String strategyClass = element.getAttribute("class"); + if (!StringUtils.hasText(strategyClass)) { strategyClass = KeyspaceAttributes.DEFAULT_REPLICATION_STRATEGY; } - Long replicationFactor = null; - if (element != null) { - String s = element.getAttribute("replication-factor"); - replicationFactor = (s == null || s.trim().length() == 0) ? null : Long.parseLong(s); - } - if (replicationFactor == null) { - replicationFactor = KeyspaceAttributes.DEFAULT_REPLICATION_FACTOR; + String replicationFactor = null; + if (strategyClass.equals(ReplicationStrategy.SIMPLE_STRATEGY.getValue())) { + + replicationFactor = element.getAttribute("replication-factor"); + + if (replicationFactor == null) { + replicationFactor = KeyspaceAttributes.DEFAULT_REPLICATION_FACTOR + ""; + } + } Map replicationMap = new HashMap(); replicationMap.put(new DefaultOption("class", String.class, false, false, true), strategyClass); - replicationMap.put(new DefaultOption("replication_factor", Long.class, true, false, false), replicationFactor); + if (replicationFactor != null) { + replicationMap.put(new DefaultOption("replication_factor", Long.class, true, false, false), replicationFactor); + } - if (element != null) { - - NodeList dataCenters = element.getElementsByTagName("data-center"); - - int length = dataCenters.getLength(); - for (int i = 0; i < length; i++) { - - Element dataCenter = (Element) dataCenters.item(i); - - replicationMap.put(new DefaultOption(dataCenter.getAttribute("name"), Long.class, false, false, true), + /* + * DataCenters only apply to NetworkTolopogyStrategy + */ + if (strategyClass.equals(ReplicationStrategy.NETWORK_TOPOLOGY_STRATEGY.getValue())) { + List dcElements = DomUtils.getChildElementsByTagName(element, "data-center"); + for (Element dataCenter : dcElements) { + replicationMap.put(new DefaultOption(dataCenter.getAttribute("name"), Long.class, true, false, true), dataCenter.getAttribute("replication-factor")); } } - return create.with(KeyspaceOption.REPLICATION, replicationMap); + builder.addPropertyValue("replicationOptions", replicationMap); + } protected String parseScript(Element element) { @@ -227,15 +252,35 @@ public class CassandraClusterParser extends AbstractSimpleBeanDefinitionParser { return builder.getBeanDefinition(); } - protected static class KeyspaceSpecifications { + /** + * Returns a {@link BeanDefinition} for a {@link AuthProvider} object. + * + * @param element + * @param context + * @return the {@link BeanDefinition} or {@literal null} if auth-info-provider is not given. + */ + private BeanDefinition getKeyspaceSpecificationBeanDefinition(Element element, ParserContext context) { - public KeyspaceSpecifications(CreateKeyspaceSpecification create, DropKeyspaceSpecification drop) { - this.create = create; - this.drop = drop; + String name = element.getAttribute("name"); + String action = element.getAttribute("action"); + String durableWrites = element.getAttribute("durable-writes"); + + if (!StringUtils.hasText(action)) { + return null; } - public CreateKeyspaceSpecification create; - public DropKeyspaceSpecification drop; - // TODO: public AlterKeyspaceSpecification alter; + BeanDefinitionBuilder keyspaceBuilder = BeanDefinitionBuilder + .genericBeanDefinition(KeyspaceActionSpecificationFactoryBean.class); + keyspaceBuilder.addPropertyValue("name", name); + keyspaceBuilder.addPropertyValue("action", action); + keyspaceBuilder.addPropertyValue("durableWrites", durableWrites); + + Element replicationElement = DomUtils.getChildElementByTagName(element, "replication"); + if (replicationElement != null) { + parseReplication(replicationElement, keyspaceBuilder); + } + + return getSourceBeanDefinition(keyspaceBuilder, context, element); } + } diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/KeyspaceNameCqlGenerator.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/KeyspaceNameCqlGenerator.java index fbaa6b7dc..66092d235 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/KeyspaceNameCqlGenerator.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/cql/generator/KeyspaceNameCqlGenerator.java @@ -15,20 +15,20 @@ */ package org.springframework.cassandra.core.cql.generator; -import org.springframework.cassandra.core.keyspace.KeyspaceNameSpecification; +import org.springframework.cassandra.core.keyspace.KeyspaceActionSpecification; import org.springframework.util.Assert; -public abstract class KeyspaceNameCqlGenerator> { +public abstract class KeyspaceNameCqlGenerator> { public abstract StringBuilder toCql(StringBuilder cql); - private KeyspaceNameSpecification specification; + private KeyspaceActionSpecification specification; - public KeyspaceNameCqlGenerator(KeyspaceNameSpecification specification) { + public KeyspaceNameCqlGenerator(KeyspaceActionSpecification specification) { setSpecification(specification); } - protected void setSpecification(KeyspaceNameSpecification specification) { + protected void setSpecification(KeyspaceActionSpecification specification) { Assert.notNull(specification); this.specification = specification; } diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DropKeyspaceSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DropKeyspaceSpecification.java index 839e793c5..5e7511f8d 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DropKeyspaceSpecification.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/DropKeyspaceSpecification.java @@ -1,28 +1,28 @@ -package org.springframework.cassandra.core.keyspace; - -public class DropKeyspaceSpecification extends KeyspaceNameSpecification { - - private boolean ifExists; - - public DropKeyspaceSpecification ifExists() { - return ifExists(true); - } - - public DropKeyspaceSpecification ifExists(boolean ifExists) { - this.ifExists = ifExists; - return this; - } - - public boolean getIfExists() { - return ifExists; - } - - /** - * Entry point into the {@link DropKeyspaceSpecification}'s fluent API to drop a keyspace. Convenient if imported - * statically. - */ - public static DropKeyspaceSpecification dropKeyspace() { - return new DropKeyspaceSpecification(); - } - -} +package org.springframework.cassandra.core.keyspace; + +public class DropKeyspaceSpecification extends KeyspaceActionSpecification { + + private boolean ifExists; + + public DropKeyspaceSpecification ifExists() { + return ifExists(true); + } + + public DropKeyspaceSpecification ifExists(boolean ifExists) { + this.ifExists = ifExists; + return this; + } + + public boolean getIfExists() { + return ifExists; + } + + /** + * Entry point into the {@link DropKeyspaceSpecification}'s fluent API to drop a keyspace. Convenient if imported + * statically. + */ + public static DropKeyspaceSpecification dropKeyspace() { + return new DropKeyspaceSpecification(); + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceActionSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceActionSpecification.java new file mode 100644 index 000000000..cddc2115d --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceActionSpecification.java @@ -0,0 +1,76 @@ +package org.springframework.cassandra.core.keyspace; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.checkIdentifier; +import static org.springframework.cassandra.core.cql.CqlStringUtils.identifize; + +/** + * Abstract builder class to support the construction of keyspace specifications. + * + * @author John McPeek + * @author David Webb + * @param The subtype of the {@link KeyspaceActionSpecification} + */ +public abstract class KeyspaceActionSpecification> { + + /** + * The name of the table. + */ + private String name; + + /** + * Sets the keyspace name. + * + * @return this + */ + @SuppressWarnings("unchecked") + public T name(String name) { + checkIdentifier(name); + this.name = name; + return (T) this; + } + + public String getName() { + return name; + } + + public String getNameAsIdentifier() { + return identifize(name); + } + + /** + * For debugging KeyspaceActionSprcifications + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Keyspace Action Specification {name: " + name + ", class: " + this.getClass() + "}"); + return sb.toString(); + } + + /** + * Determine the KeyspaceActionSpecifications are the same if they have the same "name" and same class. + * + * @param that The object to compare this to. + * @return Are this and that the same? + */ + @Override + public boolean equals(Object that) { + if (this == that) { + return true; + } + if (that == null) { + return false; + } + if (!(that instanceof KeyspaceActionSpecification)) { + return false; + } + KeyspaceActionSpecification thatSpec = (KeyspaceActionSpecification) that; + return this.name.equals(thatSpec.name) && this.getClass().equals(that.getClass()); + } + + @Override + public int hashCode() { + return this.name.hashCode() ^ this.getClass().hashCode(); + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceNameSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceNameSpecification.java deleted file mode 100644 index 0bec92437..000000000 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceNameSpecification.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.springframework.cassandra.core.keyspace; - -import static org.springframework.cassandra.core.cql.CqlStringUtils.checkIdentifier; -import static org.springframework.cassandra.core.cql.CqlStringUtils.identifize; - -/** - * Abstract builder class to support the construction of keyspace specifications. - * - * @author John McPeek - * @param The subtype of the {@link KeyspaceNameSpecification} - */ -public abstract class KeyspaceNameSpecification> { - - /** - * The name of the table. - */ - private String name; - - /** - * Sets the keyspace name. - * - * @return this - */ - @SuppressWarnings( "unchecked" ) - public T name(String name) { - checkIdentifier(name); - this.name = name; - return (T) this; - } - - public String getName() { - return name; - } - - public String getNameAsIdentifier() { - return identifize(name); - } - -} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceOption.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceOption.java index 3e5905f64..0d02f15c6 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceOption.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceOption.java @@ -1,79 +1,79 @@ -package org.springframework.cassandra.core.keyspace; - -import java.util.Map; - -public enum KeyspaceOption implements Option { - REPLICATION("replication", Map.class, true, false, false), - - DURABLE_WRITES("durable_writes", Boolean.class, false, false, false); - - private Option delegate; - - private KeyspaceOption(String name, Class type, boolean requiresValue, boolean escapesValue, boolean quotesValue) { - this.delegate = new DefaultOption(name, type, requiresValue, escapesValue, quotesValue); - } - - public Class getType() { - return delegate.getType(); - } - - public boolean takesValue() { - return delegate.takesValue(); - } - - public String getName() { - return delegate.getName(); - } - - public boolean escapesValue() { - return delegate.escapesValue(); - } - - public boolean quotesValue() { - return delegate.quotesValue(); - } - - public boolean requiresValue() { - return delegate.requiresValue(); - } - - public void checkValue(Object value) { - delegate.checkValue(value); - } - - public boolean isCoerceable(Object value) { - return delegate.isCoerceable(value); - } - - public String toString() { - return delegate.toString(); - } - - public String toString(Object value) { - return delegate.toString(value); - } - - /** - * Known Replication Strategy options. - * - * @author John McPeek - * - */ - public enum ReplicationStrategy { - SIMPLE_STRATEGY("SimpleStrategy"), NETWORK_TOPOLOGY_STRATEGY("NetworkTopologyStrategy"); - - private String value; - - private ReplicationStrategy(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - - public String toString() { - return getValue(); - } - } -} +package org.springframework.cassandra.core.keyspace; + +import java.util.Map; + +public enum KeyspaceOption implements Option { + REPLICATION("replication", Map.class, true, false, false), + + DURABLE_WRITES("durable_writes", Boolean.class, false, false, false); + + private Option delegate; + + private KeyspaceOption(String name, Class type, boolean requiresValue, boolean escapesValue, boolean quotesValue) { + this.delegate = new DefaultOption(name, type, requiresValue, escapesValue, quotesValue); + } + + public Class getType() { + return delegate.getType(); + } + + public boolean takesValue() { + return delegate.takesValue(); + } + + public String getName() { + return delegate.getName(); + } + + public boolean escapesValue() { + return delegate.escapesValue(); + } + + public boolean quotesValue() { + return delegate.quotesValue(); + } + + public boolean requiresValue() { + return delegate.requiresValue(); + } + + public void checkValue(Object value) { + delegate.checkValue(value); + } + + public boolean isCoerceable(Object value) { + return delegate.isCoerceable(value); + } + + public String toString() { + return delegate.toString(); + } + + public String toString(Object value) { + return delegate.toString(value); + } + + /** + * Known Replication Strategy options. + * + * @author John McPeek + * + */ + public enum ReplicationStrategy { + SIMPLE_STRATEGY("SimpleStrategy"), NETWORK_TOPOLOGY_STRATEGY("NetworkTopologyStrategy"); + + private String value; + + private ReplicationStrategy(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public String toString() { + return getValue(); + } + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceOptionsSpecification.java b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceOptionsSpecification.java index 574e166a6..23cf20698 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceOptionsSpecification.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/core/keyspace/KeyspaceOptionsSpecification.java @@ -1,94 +1,94 @@ -package org.springframework.cassandra.core.keyspace; - -import static org.springframework.cassandra.core.cql.CqlStringUtils.escapeSingle; -import static org.springframework.cassandra.core.cql.CqlStringUtils.singleQuote; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.springframework.cassandra.core.cql.CqlStringUtils; - -/** - * Abstract builder class to support the construction of table specifications that have table options, that is, those - * options normally specified by WITH ... AND .... - *

- * It is important to note that although this class depends on {@link KeyspaceOption} for convenient and typesafe use, it - * ultimately stores its options in a Map for flexibility. This means that - * {@link #with(KeyspaceOption)} and {@link #with(KeyspaceOption, Object)} delegate to - * {@link #with(String, Object, boolean, boolean)}. This design allows the API to support new Cassandra options as they - * are introduced without having to update the code immediately. - * - * @author John McPeek - * @param The subtype of the {@link KeyspaceOptionsSpecification}. - */ -public abstract class KeyspaceOptionsSpecification> extends - KeyspaceNameSpecification> { - - protected Map options = new LinkedHashMap(); - - @SuppressWarnings( "unchecked" ) - public T name(String name) { - return (T) super.name(name); - } - - /** - * Convenience method that calls with(option, null). - * - * @return this - */ - public T with(KeyspaceOption option) { - return with(option, null); - } - - /** - * Sets the given table option. This is a convenience method that calls - * {@link #with(String, Object, boolean, boolean)} appropriately from the given {@link KeyspaceOption} and value for that - * option. - * - * @param option The option to set. - * @param value The value of the option. Must be type-compatible with the {@link KeyspaceOption}. - * @return this - * @see #with(String, Object, boolean, boolean) - */ - public T with(KeyspaceOption option, Object value) { - option.checkValue(value); - return (T) with(option.getName(), value, option.escapesValue(), option.quotesValue()); - } - - /** - * Adds the given option by name to this keyspaces's options. - *

- * Options that have null values are considered single string options where the name of the option is the - * string to be used. Otherwise, the result of {@link Object#toString()} is considered to be the value of the option - * with the given name. The value, after conversion to string, may have embedded single quotes escaped according to - * parameter escape and may be single-quoted according to parameter quote. - * - * @param name The name of the option - * @param value The value of the option. If null, the value is ignored and the option is considered to be - * composed of only the name, otherwise the value's {@link Object#toString()} value is used. - * @param escape Whether to escape the value via {@link CqlStringUtils#escapeSingle(Object)}. Ignored if given value - * is an instance of a {@link Map}. - * @param quote Whether to quote the value via {@link CqlStringUtils#singleQuote(Object)}. Ignored if given value is - * an instance of a {@link Map}. - * @return this - */ - @SuppressWarnings("unchecked") - public T with(String name, Object value, boolean escape, boolean quote) { - if (!(value instanceof Map)) { - if (escape) { - value = escapeSingle(value); - } - if (quote) { - value = singleQuote(value); - } - } - options.put(name, value); - return (T) this; - } - - public Map getOptions() { - return Collections.unmodifiableMap(options); - } - -} +package org.springframework.cassandra.core.keyspace; + +import static org.springframework.cassandra.core.cql.CqlStringUtils.escapeSingle; +import static org.springframework.cassandra.core.cql.CqlStringUtils.singleQuote; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.cassandra.core.cql.CqlStringUtils; + +/** + * Abstract builder class to support the construction of table specifications that have table options, that is, those + * options normally specified by WITH ... AND .... + *

+ * It is important to note that although this class depends on {@link KeyspaceOption} for convenient and typesafe use, it + * ultimately stores its options in a Map for flexibility. This means that + * {@link #with(KeyspaceOption)} and {@link #with(KeyspaceOption, Object)} delegate to + * {@link #with(String, Object, boolean, boolean)}. This design allows the API to support new Cassandra options as they + * are introduced without having to update the code immediately. + * + * @author John McPeek + * @param The subtype of the {@link KeyspaceOptionsSpecification}. + */ +public abstract class KeyspaceOptionsSpecification> extends + KeyspaceActionSpecification> { + + protected Map options = new LinkedHashMap(); + + @SuppressWarnings( "unchecked" ) + public T name(String name) { + return (T) super.name(name); + } + + /** + * Convenience method that calls with(option, null). + * + * @return this + */ + public T with(KeyspaceOption option) { + return with(option, null); + } + + /** + * Sets the given table option. This is a convenience method that calls + * {@link #with(String, Object, boolean, boolean)} appropriately from the given {@link KeyspaceOption} and value for that + * option. + * + * @param option The option to set. + * @param value The value of the option. Must be type-compatible with the {@link KeyspaceOption}. + * @return this + * @see #with(String, Object, boolean, boolean) + */ + public T with(KeyspaceOption option, Object value) { + option.checkValue(value); + return (T) with(option.getName(), value, option.escapesValue(), option.quotesValue()); + } + + /** + * Adds the given option by name to this keyspaces's options. + *

+ * Options that have null values are considered single string options where the name of the option is the + * string to be used. Otherwise, the result of {@link Object#toString()} is considered to be the value of the option + * with the given name. The value, after conversion to string, may have embedded single quotes escaped according to + * parameter escape and may be single-quoted according to parameter quote. + * + * @param name The name of the option + * @param value The value of the option. If null, the value is ignored and the option is considered to be + * composed of only the name, otherwise the value's {@link Object#toString()} value is used. + * @param escape Whether to escape the value via {@link CqlStringUtils#escapeSingle(Object)}. Ignored if given value + * is an instance of a {@link Map}. + * @param quote Whether to quote the value via {@link CqlStringUtils#singleQuote(Object)}. Ignored if given value is + * an instance of a {@link Map}. + * @return this + */ + @SuppressWarnings("unchecked") + public T with(String name, Object value, boolean escape, boolean quote) { + if (!(value instanceof Map)) { + if (escape) { + value = escapeSingle(value); + } + if (quote) { + value = singleQuote(value); + } + } + options.put(name, value); + return (T) this; + } + + public Map getOptions() { + return Collections.unmodifiableMap(options); + } + +} diff --git a/spring-cassandra/src/main/resources/org/springframework/cassandra/config/spring-cassandra-1.0.xsd b/spring-cassandra/src/main/resources/org/springframework/cassandra/config/spring-cassandra-1.0.xsd index c0b1ab354..03fdb1ac6 100644 --- a/spring-cassandra/src/main/resources/org/springframework/cassandra/config/spring-cassandra-1.0.xsd +++ b/spring-cassandra/src/main/resources/org/springframework/cassandra/config/spring-cassandra-1.0.xsd @@ -133,32 +133,14 @@ The native CQL port to connect to. Default is 9042. ]]> - + - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - The type of the {@link TableNameSpecification} * @param The type of the {@link TableNameCqlGenerator} */ -public abstract class KeyspaceOperationCqlGeneratorTest, G extends KeyspaceNameCqlGenerator> { +public abstract class KeyspaceOperationCqlGeneratorTest, G extends KeyspaceNameCqlGenerator> { public abstract S specification(); diff --git a/spring-cassandra/src/test/resources/logback-test.xml b/spring-cassandra/src/test/resources/logback-test.xml index 7db55523f..a410f7fb5 100644 --- a/spring-cassandra/src/test/resources/logback-test.xml +++ b/spring-cassandra/src/test/resources/logback-test.xml @@ -10,8 +10,8 @@ - - + + diff --git a/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/PropertyPlaceholderNamespaceCreatingXmlConfigTest-context.xml b/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/PropertyPlaceholderNamespaceCreatingXmlConfigTest-context.xml new file mode 100644 index 000000000..2c7f28a31 --- /dev/null +++ b/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/PropertyPlaceholderNamespaceCreatingXmlConfigTest-context.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/ppncxct.properties b/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/ppncxct.properties new file mode 100644 index 000000000..b89cbde8e --- /dev/null +++ b/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/ppncxct.properties @@ -0,0 +1,9 @@ +cluster.contactPoints=localhost +cluster.port=9042 +cluster.compression=SNAPPY +keyspace.name=ppncxct +keyspace.action=CREATE +dc1.name=DCJAX +dc1.rf=2 +dc2.name=DCCTL +dc2.rf=3