From c1d30dcc52096fd41bdd7c8b5e463f0a3145d748 Mon Sep 17 00:00:00 2001 From: Matthew Adams Date: Mon, 9 Dec 2013 14:23:39 -0600 Subject: [PATCH] wip: beginning to move appropriate config to spring-cassandra --- .../cassandra/config/CompressionType.java | 25 + .../cassandra/config/KeyspaceAttributes.java | 107 +++++ .../config/PoolingOptionsConfig.java | 62 +++ .../cassandra/config/SocketOptionsConfig.java | 89 ++++ .../cassandra/config/TableAttributes.java | 49 ++ .../AbstractCassandraConfiguration.java | 2 +- .../cassandra/config/xml/BeanNames.java | 31 ++ .../xml/CassandraClusterFactoryBean.java | 264 ++++++++++ .../config/xml/CassandraClusterParser.java | 126 +++++ .../config/xml/CassandraKeyspaceParser.java | 131 +++++ .../config/xml/CassandraNamespaceHandler.java | 36 ++ .../xml/CassandraSessionFactoryBean.java | 157 ++++++ .../config/xml/CassandraSessionParser.java | 69 +++ .../resources/META-INF/spring.handlers.todo | 1 + .../resources/META-INF/spring.schemas.todo | 2 + .../resources/META-INF/spring.tooling.todo | 4 + .../cassandra/config/spring-cassandra-1.0.xsd | 453 ++++++++++++++++++ .../cassandra/config/spring-cassandra.gif | Bin 0 -> 581 bytes ...tractSpringDataCassandraConfiguration.java | 4 +- .../config/CassandraKeyspaceFactoryBean.java | 340 +++++++++++++ .../main/resources/META-INF/spring.tooling | 2 +- 21 files changed, 1949 insertions(+), 5 deletions(-) create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/config/CompressionType.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/config/KeyspaceAttributes.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/config/PoolingOptionsConfig.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/config/SocketOptionsConfig.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/config/TableAttributes.java rename spring-cassandra/src/main/java/org/springframework/cassandra/config/{ => java}/AbstractCassandraConfiguration.java (97%) create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/BeanNames.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraClusterFactoryBean.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraClusterParser.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraKeyspaceParser.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraNamespaceHandler.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraSessionFactoryBean.java create mode 100644 spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraSessionParser.java create mode 100644 spring-cassandra/src/main/resources/META-INF/spring.handlers.todo create mode 100644 spring-cassandra/src/main/resources/META-INF/spring.schemas.todo create mode 100644 spring-cassandra/src/main/resources/META-INF/spring.tooling.todo create mode 100644 spring-cassandra/src/main/resources/org/springframework/cassandra/config/spring-cassandra-1.0.xsd create mode 100644 spring-cassandra/src/main/resources/org/springframework/cassandra/config/spring-cassandra.gif create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraKeyspaceFactoryBean.java diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/CompressionType.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/CompressionType.java new file mode 100644 index 000000000..4e9248ada --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/CompressionType.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; + +/** + * Simple enumeration for the various compression types. + * + * @author Alex Shvid + */ +public enum CompressionType { + NONE, SNAPPY; +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/KeyspaceAttributes.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/KeyspaceAttributes.java new file mode 100644 index 000000000..53b74c182 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/KeyspaceAttributes.java @@ -0,0 +1,107 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.config; + +import java.util.Collection; + +/** + * Keyspace attributes are used for manipulation around keyspace at the startup. Auto property defines the way how to do + * this. Other attributes used to ensure or update keyspace settings. + * + * @author Alex Shvid + */ +public class KeyspaceAttributes { + + public static final String DEFAULT_REPLICATION_STRATEGY = "SimpleStrategy"; + public static final int DEFAULT_REPLICATION_FACTOR = 1; + public static final boolean DEFAULT_DURABLE_WRITES = true; + + /* + * auto possible values: + * validate: validate the keyspace, makes no changes. + * update: update the keyspace. + * create: creates the keyspace, destroying previous data. + * create-drop: drop the keyspace at the end of the session. + */ + public static final String AUTO_VALIDATE = "validate"; + public static final String AUTO_UPDATE = "update"; + public static final String AUTO_CREATE = "create"; + public static final String AUTO_CREATE_DROP = "create-drop"; + + private String auto = AUTO_VALIDATE; + private String replicationStrategy = DEFAULT_REPLICATION_STRATEGY; + private int replicationFactor = DEFAULT_REPLICATION_FACTOR; + private boolean durableWrites = DEFAULT_DURABLE_WRITES; + + private Collection tables; + + public String getAuto() { + return auto; + } + + public void setAuto(String auto) { + this.auto = auto; + } + + public boolean isValidate() { + return AUTO_VALIDATE.equals(auto); + } + + public boolean isUpdate() { + return AUTO_UPDATE.equals(auto); + } + + public boolean isCreate() { + return AUTO_CREATE.equals(auto); + } + + public boolean isCreateDrop() { + return AUTO_CREATE_DROP.equals(auto); + } + + public String getReplicationStrategy() { + return replicationStrategy; + } + + public void setReplicationStrategy(String replicationStrategy) { + this.replicationStrategy = replicationStrategy; + } + + public int getReplicationFactor() { + return replicationFactor; + } + + public void setReplicationFactor(int replicationFactor) { + this.replicationFactor = replicationFactor; + } + + public boolean isDurableWrites() { + return durableWrites; + } + + public void setDurableWrites(boolean durableWrites) { + this.durableWrites = durableWrites; + } + + public Collection getTables() { + return tables; + } + + public void setTables(Collection tables) { + this.tables = tables; + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/PoolingOptionsConfig.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/PoolingOptionsConfig.java new file mode 100644 index 000000000..497051ba2 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/PoolingOptionsConfig.java @@ -0,0 +1,62 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.config; + +/** + * Pooling options POJO. Can be remote or local. + * + * @author Alex Shvid + */ +public class PoolingOptionsConfig { + + private Integer minSimultaneousRequests; + private Integer maxSimultaneousRequests; + private Integer coreConnections; + private Integer maxConnections; + + public Integer getMinSimultaneousRequests() { + return minSimultaneousRequests; + } + + public void setMinSimultaneousRequests(Integer minSimultaneousRequests) { + this.minSimultaneousRequests = minSimultaneousRequests; + } + + public Integer getMaxSimultaneousRequests() { + return maxSimultaneousRequests; + } + + public void setMaxSimultaneousRequests(Integer maxSimultaneousRequests) { + this.maxSimultaneousRequests = maxSimultaneousRequests; + } + + public Integer getCoreConnections() { + return coreConnections; + } + + public void setCoreConnections(Integer coreConnections) { + this.coreConnections = coreConnections; + } + + public Integer getMaxConnections() { + return maxConnections; + } + + public void setMaxConnections(Integer maxConnections) { + this.maxConnections = maxConnections; + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/SocketOptionsConfig.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/SocketOptionsConfig.java new file mode 100644 index 000000000..b658f36e9 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/SocketOptionsConfig.java @@ -0,0 +1,89 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.config; + +/** + * Socket options POJO. Uses to configure Netty. + * + * @author Alex Shvid + */ +public class SocketOptionsConfig { + + private Integer connectTimeoutMls; + private Boolean keepAlive; + private Boolean reuseAddress; + private Integer soLinger; + private Boolean tcpNoDelay; + private Integer receiveBufferSize; + private Integer sendBufferSize; + + public Integer getConnectTimeoutMls() { + return connectTimeoutMls; + } + + public void setConnectTimeoutMls(Integer connectTimeoutMls) { + this.connectTimeoutMls = connectTimeoutMls; + } + + public Boolean getKeepAlive() { + return keepAlive; + } + + public void setKeepAlive(Boolean keepAlive) { + this.keepAlive = keepAlive; + } + + public Boolean getReuseAddress() { + return reuseAddress; + } + + public void setReuseAddress(Boolean reuseAddress) { + this.reuseAddress = reuseAddress; + } + + public Integer getSoLinger() { + return soLinger; + } + + public void setSoLinger(Integer soLinger) { + this.soLinger = soLinger; + } + + public Boolean getTcpNoDelay() { + return tcpNoDelay; + } + + public void setTcpNoDelay(Boolean tcpNoDelay) { + this.tcpNoDelay = tcpNoDelay; + } + + public Integer getReceiveBufferSize() { + return receiveBufferSize; + } + + public void setReceiveBufferSize(Integer receiveBufferSize) { + this.receiveBufferSize = receiveBufferSize; + } + + public Integer getSendBufferSize() { + return sendBufferSize; + } + + public void setSendBufferSize(Integer sendBufferSize) { + this.sendBufferSize = sendBufferSize; + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/TableAttributes.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/TableAttributes.java new file mode 100644 index 000000000..ce6d26137 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/TableAttributes.java @@ -0,0 +1,49 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.config; + +/** + * Table attributes are used for manipulation around table at the startup (create/update/validate). + * + * @author Alex Shvid + */ +public class TableAttributes { + + private String entity; + private String name; + + public String getEntity() { + return entity; + } + + public void setEntity(String entity) { + this.entity = entity; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "TableAttributes [entity=" + entity + "]"; + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/AbstractCassandraConfiguration.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/java/AbstractCassandraConfiguration.java similarity index 97% rename from spring-cassandra/src/main/java/org/springframework/cassandra/config/AbstractCassandraConfiguration.java rename to spring-cassandra/src/main/java/org/springframework/cassandra/config/java/AbstractCassandraConfiguration.java index 3c3465bd7..434007340 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/config/AbstractCassandraConfiguration.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/java/AbstractCassandraConfiguration.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.cassandra.config; +package org.springframework.cassandra.config.java; import org.springframework.cassandra.core.CassandraOperations; import org.springframework.cassandra.core.CassandraTemplate; diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/BeanNames.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/BeanNames.java new file mode 100644 index 000000000..aa5dabf59 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/BeanNames.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * 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.xml; + +/** + * @author Alex Shvid + * @author David Webb + */ +public final class BeanNames { + + private BeanNames() { + } + + public static final String CASSANDRA_CLUSTER = "cassandra-cluster"; + public static final String CASSANDRA_KEYSPACE = "cassandra-keyspace"; + public static final String CASSANDRA_SESSION = "cassandra-session"; + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraClusterFactoryBean.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraClusterFactoryBean.java new file mode 100644 index 000000000..f6a27af01 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraClusterFactoryBean.java @@ -0,0 +1,264 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.config.xml; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.cassandra.config.CompressionType; +import org.springframework.cassandra.config.PoolingOptionsConfig; +import org.springframework.cassandra.config.SocketOptionsConfig; +import org.springframework.cassandra.support.CassandraExceptionTranslator; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.util.StringUtils; + +import com.datastax.driver.core.AuthProvider; +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.HostDistance; +import com.datastax.driver.core.PoolingOptions; +import com.datastax.driver.core.ProtocolOptions.Compression; +import com.datastax.driver.core.SocketOptions; +import com.datastax.driver.core.policies.LoadBalancingPolicy; +import com.datastax.driver.core.policies.ReconnectionPolicy; +import com.datastax.driver.core.policies.RetryPolicy; + +/** + * Convenient factory for configuring a Cassandra Cluster. + * + * @author Alex Shvid + * @author Matthew T. Adams + */ + +public class CassandraClusterFactoryBean implements FactoryBean, InitializingBean, DisposableBean, + PersistenceExceptionTranslator { + + private static final int DEFAULT_PORT = 9042; + + private Cluster cluster; + + private String contactPoints; + private int port = DEFAULT_PORT; + private CompressionType compressionType; + + private PoolingOptionsConfig localPoolingOptions; + private PoolingOptionsConfig remotePoolingOptions; + private SocketOptionsConfig socketOptions; + + private AuthProvider authProvider; + private LoadBalancingPolicy loadBalancingPolicy; + private ReconnectionPolicy reconnectionPolicy; + private RetryPolicy retryPolicy; + + private boolean metricsEnabled = true; + + private final PersistenceExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator(); + + public Cluster getObject() throws Exception { + return cluster; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + public Class getObjectType() { + return Cluster.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#isSingleton() + */ + public boolean isSingleton() { + return true; + } + + /* + * (non-Javadoc) + * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException) + */ + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + return exceptionTranslator.translateExceptionIfPossible(ex); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception { + + if (!StringUtils.hasText(contactPoints)) { + throw new IllegalArgumentException("at least one server is required"); + } + + Cluster.Builder builder = Cluster.builder(); + + builder.addContactPoints(StringUtils.commaDelimitedListToStringArray(contactPoints)).withPort(port); + + if (compressionType != null) { + builder.withCompression(convertCompressionType(compressionType)); + } + + if (localPoolingOptions != null) { + builder.withPoolingOptions(configPoolingOptions(HostDistance.LOCAL, localPoolingOptions)); + } + + if (remotePoolingOptions != null) { + builder.withPoolingOptions(configPoolingOptions(HostDistance.REMOTE, remotePoolingOptions)); + } + + if (socketOptions != null) { + builder.withSocketOptions(configSocketOptions(socketOptions)); + } + + if (authProvider != null) { + builder.withAuthProvider(authProvider); + } + + if (loadBalancingPolicy != null) { + builder.withLoadBalancingPolicy(loadBalancingPolicy); + } + + if (reconnectionPolicy != null) { + builder.withReconnectionPolicy(reconnectionPolicy); + } + + if (retryPolicy != null) { + builder.withRetryPolicy(retryPolicy); + } + + if (!metricsEnabled) { + builder.withoutMetrics(); + } + + Cluster cluster = builder.build(); + + // initialize property + this.cluster = cluster; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ + public void destroy() throws Exception { + this.cluster.shutdown(); + } + + public void setContactPoints(String contactPoints) { + this.contactPoints = contactPoints; + } + + public void setPort(int port) { + this.port = port; + } + + public void setCompressionType(CompressionType compressionType) { + this.compressionType = compressionType; + } + + public void setLocalPoolingOptions(PoolingOptionsConfig localPoolingOptions) { + this.localPoolingOptions = localPoolingOptions; + } + + public void setRemotePoolingOptions(PoolingOptionsConfig remotePoolingOptions) { + this.remotePoolingOptions = remotePoolingOptions; + } + + public void setSocketOptions(SocketOptionsConfig socketOptions) { + this.socketOptions = socketOptions; + } + + public void setAuthProvider(AuthProvider authProvider) { + this.authProvider = authProvider; + } + + public void setLoadBalancingPolicy(LoadBalancingPolicy loadBalancingPolicy) { + this.loadBalancingPolicy = loadBalancingPolicy; + } + + public void setReconnectionPolicy(ReconnectionPolicy reconnectionPolicy) { + this.reconnectionPolicy = reconnectionPolicy; + } + + public void setRetryPolicy(RetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + } + + public void setMetricsEnabled(boolean metricsEnabled) { + this.metricsEnabled = metricsEnabled; + } + + private static Compression convertCompressionType(CompressionType type) { + switch (type) { + case NONE: + return Compression.NONE; + case SNAPPY: + return Compression.SNAPPY; + } + throw new IllegalArgumentException("unknown compression type " + type); + } + + private static PoolingOptions configPoolingOptions(HostDistance hostDistance, PoolingOptionsConfig config) { + PoolingOptions poolingOptions = new PoolingOptions(); + + if (config.getMinSimultaneousRequests() != null) { + poolingOptions + .setMinSimultaneousRequestsPerConnectionThreshold(hostDistance, config.getMinSimultaneousRequests()); + } + if (config.getMaxSimultaneousRequests() != null) { + poolingOptions + .setMaxSimultaneousRequestsPerConnectionThreshold(hostDistance, config.getMaxSimultaneousRequests()); + } + if (config.getCoreConnections() != null) { + poolingOptions.setCoreConnectionsPerHost(hostDistance, config.getCoreConnections()); + } + if (config.getMaxConnections() != null) { + poolingOptions.setMaxConnectionsPerHost(hostDistance, config.getMaxConnections()); + } + + return poolingOptions; + } + + private static SocketOptions configSocketOptions(SocketOptionsConfig config) { + SocketOptions socketOptions = new SocketOptions(); + + if (config.getConnectTimeoutMls() != null) { + socketOptions.setConnectTimeoutMillis(config.getConnectTimeoutMls()); + } + if (config.getKeepAlive() != null) { + socketOptions.setKeepAlive(config.getKeepAlive()); + } + if (config.getReuseAddress() != null) { + socketOptions.setReuseAddress(config.getReuseAddress()); + } + if (config.getSoLinger() != null) { + socketOptions.setSoLinger(config.getSoLinger()); + } + if (config.getTcpNoDelay() != null) { + socketOptions.setTcpNoDelay(config.getTcpNoDelay()); + } + if (config.getReceiveBufferSize() != null) { + socketOptions.setReceiveBufferSize(config.getReceiveBufferSize()); + } + if (config.getSendBufferSize() != null) { + socketOptions.setSendBufferSize(config.getSendBufferSize()); + } + + return socketOptions; + } +} 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 new file mode 100644 index 000000000..fc3201e0e --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraClusterParser.java @@ -0,0 +1,126 @@ +/* + * Copyright 2011-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.xml; + +import java.util.List; + +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.xml.ParserContext; +import org.springframework.cassandra.config.CompressionType; +import org.springframework.cassandra.config.PoolingOptionsConfig; +import org.springframework.cassandra.config.SocketOptionsConfig; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * Parser for <cluster;gt; definitions. + * + * @author Alex Shvid + * @author Matthew T. Adams + */ + +public class CassandraClusterParser extends AbstractSimpleBeanDefinitionParser { + + @Override + protected Class getBeanClass(Element element) { + return CassandraClusterFactoryBean.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#resolveId(org.w3c.dom.Element, org.springframework.beans.factory.support.AbstractBeanDefinition, org.springframework.beans.factory.xml.ParserContext) + */ + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + + String id = super.resolveId(element, definition, parserContext); + return StringUtils.hasText(id) ? id : BeanNames.CASSANDRA_CLUSTER; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + + String contactPoints = element.getAttribute("contactPoints"); + if (StringUtils.hasText(contactPoints)) { + builder.addPropertyValue("contactPoints", contactPoints); + } + + String port = element.getAttribute("port"); + if (StringUtils.hasText(port)) { + builder.addPropertyValue("port", port); + } + + String compression = element.getAttribute("compression"); + if (StringUtils.hasText(compression)) { + builder.addPropertyValue("compressionType", CompressionType.valueOf(compression)); + } + + postProcess(builder, element); + } + + @Override + protected void postProcess(BeanDefinitionBuilder builder, Element element) { + List subElements = DomUtils.getChildElements(element); + + // parse nested elements + for (Element subElement : subElements) { + String name = subElement.getLocalName(); + + if ("local-pooling-options".equals(name)) { + builder.addPropertyValue("localPoolingOptions", parsePoolingOptions(subElement)); + } else if ("remote-pooling-options".equals(name)) { + builder.addPropertyValue("remotePoolingOptions", parsePoolingOptions(subElement)); + } else if ("socket-options".equals(name)) { + builder.addPropertyValue("socketOptions", parseSocketOptions(subElement)); + } + } + + } + + private BeanDefinition parsePoolingOptions(Element element) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(PoolingOptionsConfig.class); + + // TODO + // ParsingUtils.setPropertyValue(builder, element, "min-simultaneous-requests", "minSimultaneousRequests"); + // ParsingUtils.setPropertyValue(builder, element, "max-simultaneous-requests", "maxSimultaneousRequests"); + // ParsingUtils.setPropertyValue(builder, element, "core-connections", "coreConnections"); + // ParsingUtils.setPropertyValue(builder, element, "max-connections", "maxConnections"); + + return builder.getBeanDefinition(); + } + + private BeanDefinition parseSocketOptions(Element element) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SocketOptionsConfig.class); + + // TODO + // ParsingUtils.setPropertyValue(builder, element, "connect-timeout-mls", "connectTimeoutMls"); + // ParsingUtils.setPropertyValue(builder, element, "keep-alive", "keepAlive"); + // ParsingUtils.setPropertyValue(builder, element, "reuse-address", "reuseAddress"); + // ParsingUtils.setPropertyValue(builder, element, "so-linger", "soLinger"); + // ParsingUtils.setPropertyValue(builder, element, "tcp-no-delay", "tcpNoDelay"); + // ParsingUtils.setPropertyValue(builder, element, "receive-buffer-size", "receiveBufferSize"); + // ParsingUtils.setPropertyValue(builder, element, "send-buffer-size", "sendBufferSize"); + + return builder.getBeanDefinition(); + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraKeyspaceParser.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraKeyspaceParser.java new file mode 100644 index 000000000..37ee4988f --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraKeyspaceParser.java @@ -0,0 +1,131 @@ +/* + * Copyright 2011-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.xml; + +import java.util.List; + +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.support.ManagedList; +import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.cassandra.config.KeyspaceAttributes; +import org.springframework.cassandra.config.TableAttributes; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * Parser for <keyspace;gt; definitions. + * + * @author Alex Shvid + */ + +public class CassandraKeyspaceParser extends AbstractSimpleBeanDefinitionParser { + + @Override + protected Class getBeanClass(Element element) { + return CassandraSessionFactoryBean.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#resolveId(org.w3c.dom.Element, org.springframework.beans.factory.support.AbstractBeanDefinition, org.springframework.beans.factory.xml.ParserContext) + */ + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + + String id = super.resolveId(element, definition, parserContext); + return StringUtils.hasText(id) ? id : BeanNames.CASSANDRA_KEYSPACE; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + + String name = element.getAttribute("name"); + if (StringUtils.hasText(name)) { + builder.addPropertyValue("keyspace", name); + } + + String clusterRef = element.getAttribute("cassandra-cluster-ref"); + if (!StringUtils.hasText(clusterRef)) { + clusterRef = BeanNames.CASSANDRA_CLUSTER; + } + builder.addPropertyReference("cluster", clusterRef); + + String converterRef = element.getAttribute("cassandra-converter-ref"); + if (StringUtils.hasText(converterRef)) { + builder.addPropertyReference("converter", converterRef); + } + + postProcess(builder, element); + } + + @Override + protected void postProcess(BeanDefinitionBuilder builder, Element element) { + List subElements = DomUtils.getChildElements(element); + + // parse nested elements + for (Element subElement : subElements) { + String name = subElement.getLocalName(); + + if ("keyspace-attributes".equals(name)) { + builder.addPropertyValue("keyspaceAttributes", parseKeyspaceAttributes(subElement)); + } + } + + } + + private BeanDefinition parseKeyspaceAttributes(Element element) { + BeanDefinitionBuilder defBuilder = BeanDefinitionBuilder.genericBeanDefinition(KeyspaceAttributes.class); + + // TODO + // ParsingUtils.setPropertyValue(defBuilder, element, "auto", "auto"); + // ParsingUtils.setPropertyValue(defBuilder, element, "replication-strategy", "replicationStrategy"); + // ParsingUtils.setPropertyValue(defBuilder, element, "replication-factor", "replicationFactor"); + // ParsingUtils.setPropertyValue(defBuilder, element, "durable-writes", "durableWrites"); + + List subElements = DomUtils.getChildElements(element); + ManagedList tables = new ManagedList(subElements.size()); + + // parse nested elements + for (Element subElement : subElements) { + String name = subElement.getLocalName(); + + if ("table".equals(name)) { + tables.add(parseTable(subElement)); + } + } + if (!tables.isEmpty()) { + defBuilder.addPropertyValue("tables", tables); + } + + return defBuilder.getBeanDefinition(); + } + + private BeanDefinition parseTable(Element element) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(TableAttributes.class); + + // TODO + // ParsingUtils.setPropertyValue(builder, element, "entity", "entity"); + // ParsingUtils.setPropertyValue(builder, element, "name", "name"); + + return builder.getBeanDefinition(); + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraNamespaceHandler.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraNamespaceHandler.java new file mode 100644 index 000000000..0a2ad6518 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraNamespaceHandler.java @@ -0,0 +1,36 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.config.xml; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * Namespace handler for <cassandra;gt;. + * + * @author Alex Shvid + */ + +public class CassandraNamespaceHandler extends NamespaceHandlerSupport { + + public void init() { + + registerBeanDefinitionParser("cluster", new CassandraClusterParser()); + registerBeanDefinitionParser("keyspace", new CassandraKeyspaceParser()); + registerBeanDefinitionParser("session", new CassandraSessionParser()); + + } + +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraSessionFactoryBean.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraSessionFactoryBean.java new file mode 100644 index 000000000..0be9a5feb --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraSessionFactoryBean.java @@ -0,0 +1,157 @@ +/* + * Copyright 2011-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cassandra.config.xml; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.cassandra.config.KeyspaceAttributes; +import org.springframework.cassandra.support.CassandraExceptionTranslator; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.util.StringUtils; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.KeyspaceMetadata; +import com.datastax.driver.core.Session; + +/** + * Convenient factory for configuring a Cassandra Session. Session is a thread safe singleton and created per a + * keyspace. So, it is enough to have one session per application. + * + * @author Alex Shvid + * @author Matthew T. Adams + */ + +public class CassandraSessionFactoryBean implements FactoryBean, InitializingBean, DisposableBean, + BeanClassLoaderAware, PersistenceExceptionTranslator { + + private static final Logger log = LoggerFactory.getLogger(CassandraSessionFactoryBean.class); + + public static final String DEFAULT_REPLICATION_STRATEGY = "SimpleStrategy"; + public static final int DEFAULT_REPLICATION_FACTOR = 1; + + private ClassLoader beanClassLoader; + + private Cluster cluster; + private Session session; + private String keyspace; + + private KeyspaceAttributes keyspaceAttributes; + + private final PersistenceExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator(); + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + public Session getObject() { + return session; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + public Class getObjectType() { + return Session.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#isSingleton() + */ + public boolean isSingleton() { + return true; + } + + /* + * (non-Javadoc) + * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException) + */ + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + return exceptionTranslator.translateExceptionIfPossible(ex); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception { + + if (cluster == null) { + throw new IllegalArgumentException("at least one cluster is required"); + } + + this.session = StringUtils.hasText(this.keyspace) ? cluster.connect(keyspace) : cluster.connect(); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ + public void destroy() throws Exception { + + this.session.shutdown(); + } + + public void setKeyspace(String keyspace) { + this.keyspace = keyspace; + } + + public void setCluster(Cluster cluster) { + this.cluster = cluster; + } + + public void setKeyspaceAttributes(KeyspaceAttributes keyspaceAttributes) { + this.keyspaceAttributes = keyspaceAttributes; + } + + private static String compareKeyspaceAttributes(KeyspaceAttributes keyspaceAttributes, + KeyspaceMetadata keyspaceMetadata) { + if (keyspaceAttributes.isDurableWrites() != keyspaceMetadata.isDurableWrites()) { + return "durableWrites"; + } + Map replication = keyspaceMetadata.getReplication(); + String replicationFactorStr = replication.get("replication_factor"); + if (replicationFactorStr == null) { + return "replication_factor"; + } + try { + int replicationFactor = Integer.parseInt(replicationFactorStr); + if (keyspaceAttributes.getReplicationFactor() != replicationFactor) { + return "replication_factor"; + } + } catch (NumberFormatException e) { + return "replication_factor"; + } + + String attributesStrategy = keyspaceAttributes.getReplicationStrategy(); + if (attributesStrategy.indexOf('.') == -1) { + attributesStrategy = "org.apache.cassandra.locator." + attributesStrategy; + } + String replicationStrategy = replication.get("class"); + if (!attributesStrategy.equals(replicationStrategy)) { + return "replication_class"; + } + return null; + } +} diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraSessionParser.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraSessionParser.java new file mode 100644 index 000000000..357ce17a6 --- /dev/null +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraSessionParser.java @@ -0,0 +1,69 @@ +/* + * Copyright 2011-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.xml; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +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.xml.ParserContext; +import org.springframework.cassandra.core.SessionFactoryBean; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * Parser for <session;gt; definitions. + * + * @author David Webb + */ + +public class CassandraSessionParser extends AbstractSimpleBeanDefinitionParser { + + @Override + protected Class getBeanClass(Element element) { + return SessionFactoryBean.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#resolveId(org.w3c.dom.Element, org.springframework.beans.factory.support.AbstractBeanDefinition, org.springframework.beans.factory.xml.ParserContext) + */ + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + + String id = super.resolveId(element, definition, parserContext); + return StringUtils.hasText(id) ? id : BeanNames.CASSANDRA_SESSION; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + + String keyspaceRef = element.getAttribute("cassandra-keyspace-ref"); + if (!StringUtils.hasText(keyspaceRef)) { + keyspaceRef = BeanNames.CASSANDRA_KEYSPACE; + } + builder.addPropertyReference("keyspace", keyspaceRef); + + postProcess(builder, element); + } + + @Override + protected void postProcess(BeanDefinitionBuilder builder, Element element) { + + } + +} diff --git a/spring-cassandra/src/main/resources/META-INF/spring.handlers.todo b/spring-cassandra/src/main/resources/META-INF/spring.handlers.todo new file mode 100644 index 000000000..f5bd3781b --- /dev/null +++ b/spring-cassandra/src/main/resources/META-INF/spring.handlers.todo @@ -0,0 +1 @@ +http\://www.springframework.org/schema/cassandra=org.springframework.cassandra.config.xml.CassandraNamespaceHandler diff --git a/spring-cassandra/src/main/resources/META-INF/spring.schemas.todo b/spring-cassandra/src/main/resources/META-INF/spring.schemas.todo new file mode 100644 index 000000000..9a41e8fcd --- /dev/null +++ b/spring-cassandra/src/main/resources/META-INF/spring.schemas.todo @@ -0,0 +1,2 @@ +http\://www.springframework.org/schema/cassandra/spring-cassandra-1.0.xsd=org/springframework/cassandra/config/spring-cassandra-1.0.xsd +http\://www.springframework.org/schema/cassandra/spring-cassandra.xsd=org/springframework/cassandra/config/spring-cassandra-1.0.xsd \ No newline at end of file diff --git a/spring-cassandra/src/main/resources/META-INF/spring.tooling.todo b/spring-cassandra/src/main/resources/META-INF/spring.tooling.todo new file mode 100644 index 000000000..a339bc54e --- /dev/null +++ b/spring-cassandra/src/main/resources/META-INF/spring.tooling.todo @@ -0,0 +1,4 @@ +# Tooling related information for the cassandra namespace +http\://www.springframework.org/schema/cassandra@name=Spring Cassandra Namespace +http\://www.springframework.org/schema/cassandra@prefix=cassandra +http\://www.springframework.org/schema/cassandra@icon=org/springframework/data/cassandra/config/spring-cassandra.gif 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 new file mode 100644 index 000000000..98c1f9eff --- /dev/null +++ b/spring-cassandra/src/main/resources/org/springframework/cassandra/config/spring-cassandra-1.0.xsd @@ -0,0 +1,453 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the Cassandra Cluster definition (by + default "cassandra-cluster") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the Keyspace definition (by default + "cassandra-keyspace") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-cassandra/src/main/resources/org/springframework/cassandra/config/spring-cassandra.gif b/spring-cassandra/src/main/resources/org/springframework/cassandra/config/spring-cassandra.gif new file mode 100644 index 0000000000000000000000000000000000000000..20ed1f9a4438054835c3bd7231c59dcc36d9f24e GIT binary patch literal 581 zcmZ?wbhEHb6krfwc*ekR>cRs}r)GW6W=W@M$1Xhi{Pm|(La%4WG|h-<))@;Tnzydr ze|7(^se4|>h4R zUy{Z383{M%q|7Yz$#T`0m|!y{*)GRZ@9L!3yxD&uuMPIl1|0Luj6Z zdPkV$k=o$-X|CH!1CBLB?5vH+vQyt$61=G-B*R91O}5{ryoi-KQOi@qrbqb9j0v0; z5;QF^;Q#;s3^WFcKUo+V7~&apK=y#*gn@lgLwr+nOKUS18yg1`6IWXkmxPoeFOQ^9 zUmMe;Dbs|Q`WZ#VWF+Oqg&7ylnJUT8uzIpIkARk*zGf@q8bK!uB^^Jrmfe#@w3X~5 zjco%=nvW{7>YA$?xVgIcdp2DZF^sU&u#O1|bhtZ*RXNt(TP-*$)Z^}A1#USb$B=N< rFkfeu(hb`mG&f7x?8#9)=yFn#m0d_d!a, InitializingBean, DisposableBean, + BeanClassLoaderAware, PersistenceExceptionTranslator { + + private static final Logger log = LoggerFactory.getLogger(CassandraKeyspaceFactoryBean.class); + + public static final String DEFAULT_REPLICATION_STRATEGY = "SimpleStrategy"; + public static final int DEFAULT_REPLICATION_FACTOR = 1; + + private ClassLoader beanClassLoader; + + private Cluster cluster; + private Session session; + private String keyspace; + + private CassandraConverter converter; + private MappingContext, CassandraPersistentProperty> mappingContext; + + private SpringDataKeyspace keyspaceBean; + + private KeyspaceAttributes keyspaceAttributes; + + private final PersistenceExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator(); + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + public SpringDataKeyspace getObject() { + return keyspaceBean; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + public Class getObjectType() { + return Session.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#isSingleton() + */ + public boolean isSingleton() { + return true; + } + + /* + * (non-Javadoc) + * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException) + */ + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + return exceptionTranslator.translateExceptionIfPossible(ex); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception { + + if (this.converter == null) { + this.converter = getDefaultCassandraConverter(); + } + this.mappingContext = this.converter.getMappingContext(); + + if (cluster == null) { + throw new IllegalArgumentException("at least one cluster is required"); + } + + Session session = null; + session = cluster.connect(); + + if (StringUtils.hasText(keyspace)) { + + KeyspaceMetadata keyspaceMetadata = cluster.getMetadata().getKeyspace(keyspace.toLowerCase()); + boolean keyspaceExists = keyspaceMetadata != null; + boolean keyspaceCreated = false; + + if (keyspaceExists) { + log.info("keyspace exists " + keyspaceMetadata.asCQLQuery()); + } + + if (keyspaceAttributes == null) { + keyspaceAttributes = new KeyspaceAttributes(); + } + + // drop the old keyspace if needed + if (keyspaceExists && (keyspaceAttributes.isCreate() || keyspaceAttributes.isCreateDrop())) { + log.info("Drop keyspace " + keyspace + " on afterPropertiesSet"); + session.execute("DROP KEYSPACE " + keyspace + ";"); + keyspaceExists = false; + } + + // create the new keyspace if needed + if (!keyspaceExists + && (keyspaceAttributes.isCreate() || keyspaceAttributes.isCreateDrop() || keyspaceAttributes.isUpdate())) { + + String query = String + .format( + "CREATE KEYSPACE %1$s WITH replication = { 'class' : '%2$s', 'replication_factor' : %3$d } AND DURABLE_WRITES = %4$b", + keyspace, keyspaceAttributes.getReplicationStrategy(), keyspaceAttributes.getReplicationFactor(), + keyspaceAttributes.isDurableWrites()); + + log.info("Create keyspace " + keyspace + " on afterPropertiesSet " + query); + + session.execute(query); + keyspaceCreated = true; + } + + // update keyspace if needed + if (keyspaceAttributes.isUpdate() && !keyspaceCreated) { + + if (compareKeyspaceAttributes(keyspaceAttributes, keyspaceMetadata) != null) { + + String query = String + .format( + "ALTER KEYSPACE %1$s WITH replication = { 'class' : '%2$s', 'replication_factor' : %3$d } AND DURABLE_WRITES = %4$b", + keyspace, keyspaceAttributes.getReplicationStrategy(), keyspaceAttributes.getReplicationFactor(), + keyspaceAttributes.isDurableWrites()); + + log.info("Update keyspace " + keyspace + " on afterPropertiesSet " + query); + session.execute(query); + } + + } + + // validate keyspace if needed + if (keyspaceAttributes.isValidate()) { + + if (!keyspaceExists) { + throw new InvalidDataAccessApiUsageException("keyspace '" + keyspace + "' not found in the Cassandra"); + } + + String errorField = compareKeyspaceAttributes(keyspaceAttributes, keyspaceMetadata); + if (errorField != null) { + throw new InvalidDataAccessApiUsageException(errorField + " attribute is not much in the keyspace '" + + keyspace + "'"); + } + + } + + session.execute("USE " + keyspace); + + if (!CollectionUtils.isEmpty(keyspaceAttributes.getTables())) { + + for (TableAttributes tableAttributes : keyspaceAttributes.getTables()) { + + String entityClassName = tableAttributes.getEntity(); + Class entityClass = ClassUtils.forName(entityClassName, this.beanClassLoader); + CassandraPersistentEntity entity = determineEntity(entityClass); + String useTableName = tableAttributes.getName() != null ? tableAttributes.getName() : entity.getTable(); + + if (keyspaceCreated) { + createNewTable(session, useTableName, entity); + } else if (keyspaceAttributes.isUpdate()) { + TableMetadata table = keyspaceMetadata.getTable(useTableName.toLowerCase()); + if (table == null) { + createNewTable(session, useTableName, entity); + } else { + // alter table columns + for (String cql : CqlUtils.alterTable(useTableName, entity, table)) { + log.info("Execute on keyspace " + keyspace + " CQL " + cql); + session.execute(cql); + } + } + } else if (keyspaceAttributes.isValidate()) { + TableMetadata table = keyspaceMetadata.getTable(useTableName.toLowerCase()); + if (table == null) { + throw new InvalidDataAccessApiUsageException("not found table " + useTableName + " for entity " + + entityClassName); + } + // validate columns + List alter = CqlUtils.alterTable(useTableName, entity, table); + if (!alter.isEmpty()) { + throw new InvalidDataAccessApiUsageException("invalid table " + useTableName + " for entity " + + entityClassName + ". modify it by " + alter); + } + } + + // System.out.println("tableAttributes, entityClass=" + entityClass + ", table = " + entity.getTable()); + + } + } + + } + + // initialize property + this.session = session; + + this.keyspaceBean = new SpringDataKeyspace(keyspace, session, converter); + } + + private void createNewTable(Session session, String useTableName, CassandraPersistentEntity entity) + throws NoHostAvailableException { + String cql = CqlUtils.createTable(useTableName, entity, converter); + log.info("Execute on keyspace " + keyspace + " CQL " + cql); + session.execute(cql); + for (String indexCQL : CqlUtils.createIndexes(useTableName, entity)) { + log.info("Execute on keyspace " + keyspace + " CQL " + indexCQL); + session.execute(indexCQL); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ + public void destroy() throws Exception { + + if (StringUtils.hasText(keyspace) && keyspaceAttributes != null && keyspaceAttributes.isCreateDrop()) { + log.info("Drop keyspace " + keyspace + " on destroy"); + session.execute("USE system"); + session.execute("DROP KEYSPACE " + keyspace); + } + this.session.shutdown(); + } + + public void setKeyspace(String keyspace) { + this.keyspace = keyspace; + } + + public void setCluster(Cluster cluster) { + this.cluster = cluster; + } + + public void setKeyspaceAttributes(KeyspaceAttributes keyspaceAttributes) { + this.keyspaceAttributes = keyspaceAttributes; + } + + public void setConverter(CassandraConverter converter) { + this.converter = converter; + } + + private static String compareKeyspaceAttributes(KeyspaceAttributes keyspaceAttributes, + KeyspaceMetadata keyspaceMetadata) { + if (keyspaceAttributes.isDurableWrites() != keyspaceMetadata.isDurableWrites()) { + return "durableWrites"; + } + Map replication = keyspaceMetadata.getReplication(); + String replicationFactorStr = replication.get("replication_factor"); + if (replicationFactorStr == null) { + return "replication_factor"; + } + try { + int replicationFactor = Integer.parseInt(replicationFactorStr); + if (keyspaceAttributes.getReplicationFactor() != replicationFactor) { + return "replication_factor"; + } + } catch (NumberFormatException e) { + return "replication_factor"; + } + + String attributesStrategy = keyspaceAttributes.getReplicationStrategy(); + if (attributesStrategy.indexOf('.') == -1) { + attributesStrategy = "org.apache.cassandra.locator." + attributesStrategy; + } + String replicationStrategy = replication.get("class"); + if (!attributesStrategy.equals(replicationStrategy)) { + return "replication_class"; + } + return null; + } + + CassandraPersistentEntity determineEntity(Class entityClass) { + + if (entityClass == null) { + throw new InvalidDataAccessApiUsageException( + "No class parameter provided, entity table name can't be determined!"); + } + + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + if (entity == null) { + throw new InvalidDataAccessApiUsageException("No Persitent Entity information found for the class " + + entityClass.getName()); + } + return entity; + } + + private static final CassandraConverter getDefaultCassandraConverter() { + MappingCassandraConverter converter = new MappingCassandraConverter(new CassandraMappingContext()); + converter.afterPropertiesSet(); + return converter; + } +} diff --git a/spring-data-cassandra/src/main/resources/META-INF/spring.tooling b/spring-data-cassandra/src/main/resources/META-INF/spring.tooling index 3769be0bd..bdc47bdbb 100644 --- a/spring-data-cassandra/src/main/resources/META-INF/spring.tooling +++ b/spring-data-cassandra/src/main/resources/META-INF/spring.tooling @@ -1,4 +1,4 @@ # Tooling related information for the cassandra namespace -http\://www.springframework.org/schema/data/cassandra@name=Cassandra Namespace +http\://www.springframework.org/schema/data/cassandra@name=Spring Data Cassandra Namespace http\://www.springframework.org/schema/data/cassandra@prefix=cassandra http\://www.springframework.org/schema/data/cassandra@icon=org/springframework/data/cassandra/config/spring-cassandra.gif