DATACASS-219 - On startup CREATE TABLE from entities should only add 'if not exists'.

(cherry picked from commit a1e75b3857)
Signed-off-by: John Blum <jblum@pivotal.io>
This commit is contained in:
John Blum
2016-05-09 10:12:38 -07:00
parent c76689b525
commit 71889b78b4
16 changed files with 1228 additions and 287 deletions

47
pom.xml
View File

@@ -1,8 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>1.8.2.BUILD-SNAPSHOT</version>
<relativePath>../spring-data-build/parent/pom.xml</relativePath>
</parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-cassandra-parent</artifactId>
<version>1.4.2.BUILD-SNAPSHOT</version>
@@ -12,13 +21,6 @@
<description>Spring Data Cassandra</description>
<url>http://www.springsource.org/spring-data/cassandra</url>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>1.8.2.BUILD-SNAPSHOT</version>
<relativePath>../spring-data-build/parent/pom.xml</relativePath>
</parent>
<modules>
<module>spring-cql</module>
<module>spring-data-cassandra</module>
@@ -75,6 +77,20 @@
</developer>
</developers>
<repositories>
<repository>
<id>spring-libs-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-plugins-release</id>
<url>https://repo.spring.io/plugins-release</url>
</pluginRepository>
</pluginRepositories>
<dependencyManagement>
<dependencies>
<dependency>
@@ -210,20 +226,6 @@
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-libs-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-plugins-release</id>
<url>https://repo.spring.io/plugins-release</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>com.datastax.cassandra</groupId>
@@ -353,6 +355,7 @@
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>

View File

@@ -1,14 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cql</artifactId>
<name>Spring CQL</name>
<description>Raw Cassandra CQL Support for Spring Core</description>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-cassandra-parent</artifactId>
@@ -16,6 +12,12 @@
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-cql</artifactId>
<name>Spring CQL</name>
<description>Raw Cassandra CQL Support for Spring Core</description>
<url>https://github.com/spring-projects/spring-data-cassandra/tree/master/spring-cql</url>
<properties>
<validation>1.0.0.GA</validation>
</properties>

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cassandra.config;
import java.util.ArrayList;
@@ -24,136 +25,210 @@ 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.CqlOperations;
import org.springframework.cassandra.core.CqlTemplate;
import org.springframework.cassandra.support.CassandraExceptionTranslator;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Session;
/**
* Factory for configuring a Cassandra {@link Session}, which is a thread-safe singleton. As such, it is sufficient to
* have one {@link Session} per application and keyspace.
* Factory for creating and configuring a Cassandra {@link Session}, which is a thread-safe singleton.
* As such, it is sufficient to have one {@link Session} per application and keyspace.
*
* @author Alex Shvid
* @author Matthew T. Adams
* @author John Blum
* @see org.springframework.beans.factory.DisposableBean
* @see org.springframework.beans.factory.FactoryBean
* @see org.springframework.beans.factory.InitializingBean
* @see org.springframework.dao.support.PersistenceExceptionTranslator
* @see org.springframework.cassandra.core.CqlTemplate
* @see org.springframework.cassandra.support.CassandraExceptionTranslator
* @see com.datastax.driver.core.Cluster
* @see com.datastax.driver.core.Session
*/
@SuppressWarnings("unused")
public class CassandraCqlSessionFactoryBean implements FactoryBean<Session>, InitializingBean, DisposableBean,
PersistenceExceptionTranslator {
private static final Logger log = LoggerFactory.getLogger(CassandraCqlSessionFactoryBean.class);
private Cluster cluster;
private List<String> startupScripts = Collections.emptyList();
private List<String> shutdownScripts = Collections.emptyList();
protected final Logger logger = LoggerFactory.getLogger(getClass());
protected Cluster cluster;
protected Session session;
protected String keyspaceName;
protected List<String> startupScripts = new ArrayList<String>();
protected List<String> shutdownScripts = new ArrayList<String>();
protected final PersistenceExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator();
private Session session;
private String keyspaceName;
/* (non-Javadoc) */
@Override
public Session getObject() {
return session;
return this.session;
}
/* (non-Javadoc) */
@Override
public Class<? extends Session> getObjectType() {
return Session.class;
return (this.session != null ? this.session.getClass() : Session.class);
}
/* (non-Javadoc) */
@Override
public boolean isSingleton() {
return true;
}
@Override
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return exceptionTranslator.translateExceptionIfPossible(ex);
}
/* (non-Javadoc) */
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(cluster);
session = StringUtils.hasText(keyspaceName) ? cluster.connect(keyspaceName) : cluster.connect();
executeScripts(startupScripts);
this.session = connect(getKeyspaceName());
executeScripts(getStartupScripts());
}
/**
* Executes given scripts. Session must be connected when this method is called.
*/
protected void executeScripts(List<String> scripts) {
if (scripts == null || scripts.size() == 0) {
return;
}
CqlTemplate template = new CqlTemplate(session);
for (String script : scripts) {
if (log.isInfoEnabled()) {
log.info("executing raw CQL [{}]", script);
}
template.execute(script);
}
/* (non-Javadoc) */
Session connect(String keyspaceName) {
return (StringUtils.hasText(keyspaceName) ? getCluster().connect(keyspaceName) : getCluster().connect());
}
/* (non-Javadoc) */
@Override
public void destroy() throws Exception {
executeScripts(shutdownScripts);
session.close();
executeScripts(getShutdownScripts());
getSession().close();
}
/**
* Sets the keyspace name to connect to. Using <code>null</code>, empty string, or only whitespace will cause the
* system keyspace to be used.
* Executes the given Cassandra CQL scripts. The {@link Session} must be connected when this method is called.
*/
protected void executeScripts(List<String> scripts) {
if (!CollectionUtils.isEmpty(scripts)) {
CqlOperations template = newCqlOperations(getSession());
for (String script : scripts) {
logger.info("executing raw CQL [{}]", script);
template.execute(script);
}
}
}
/* (non-Javadoc) */
CqlOperations newCqlOperations(Session session) {
return new CqlTemplate(session);
}
/* (non-Javadoc) */
@Override
public DataAccessException translateExceptionIfPossible(RuntimeException e) {
return this.exceptionTranslator.translateExceptionIfPossible(e);
}
/**
* Null-safe operation to determine whether the Cassandra {@link Session} is connected or not.
*
* @return a boolean value indicating whether the Cassandra {@link Session} is connected.
* @see com.datastax.driver.core.Session#isClosed()
* @see #getObject()
*/
public boolean isConnected() {
Session session = getObject();
return !(session == null || session.isClosed());
}
/**
* Sets a reference to the Cassandra {@link Cluster} to use.
*
* @param cluster a reference to the Cassandra {@link Cluster} used by this application.
* @throws IllegalArgumentException if the {@link Cluster} reference is null.
* @see com.datastax.driver.core.Cluster
* @see #getCluster()
*/
public void setCluster(Cluster cluster) {
Assert.notNull(cluster, "Cluster must not be null");
this.cluster = cluster;
}
/**
* Returns a reference to the configured Cassandra {@link Cluster} used by this application.
*
* @return a reference to the configured Cassandra {@link Cluster}.
* @throws IllegalStateException if the reference to the {@link Cluster} was not properly initialized.
* @see com.datastax.driver.core.Cluster
* @see #setCluster(Cluster)
*/
protected Cluster getCluster() {
Assert.state(this.cluster != null, "Cluster was not properly initialized");
return this.cluster;
}
/**
* Sets the name of the Cassandra Keyspace to connect to. Passing <code>null</code>, an empty String,
* or whitespace will cause the Cassandra System Keyspace to be used.
*
* @param keyspaceName a String indicating the name of the Keyspace in which to connect.
* @see #getKeyspaceName()
*/
public void setKeyspaceName(String keyspaceName) {
this.keyspaceName = keyspaceName;
}
/**
* Sets the cluster to use. Must not be null.
* Gets the name of the Cassandra Keyspace to connect to.
*
* @return the name of the Cassandra Keyspace to connect to as a String.
* @see #setKeyspaceName(String)
*/
public void setCluster(Cluster cluster) {
if (cluster == null) {
throw new IllegalArgumentException("cluster must not be null");
}
this.cluster = cluster;
protected String getKeyspaceName() {
return this.keyspaceName;
}
/**
* Returns a reference to the connected Cassandra {@link Session}.
*
* @return a reference to the connected Cassandra {@link Session}.
* @throws IllegalStateException if the Cassandra {@link Session} was not properly initialized.
* @see com.datastax.driver.core.Session
*/
protected Session getSession() {
Session session = getObject();
Assert.state(session != null, "Session was not properly initialized");
return session;
}
/**
* Sets CQL scripts to be executed immediately after the session is connected.
*/
public void setStartupScripts(List<String> scripts) {
this.startupScripts = scripts == null ? new ArrayList<String>() : new ArrayList<String>(scripts);
this.startupScripts = (scripts != null ? new ArrayList<String>(scripts) : Collections.<String>emptyList());
}
/**
* Returns an unmodifiable list of startup scripts.
*/
public List<String> getStartupScripts() {
return Collections.unmodifiableList(startupScripts);
return Collections.unmodifiableList(this.startupScripts);
}
/**
* Sets CQL scripts to be executed immediately before the session is shutdown.
*/
public void setShutdownScripts(List<String> scripts) {
this.shutdownScripts = scripts == null ? new ArrayList<String>() : new ArrayList<String>(scripts);
this.shutdownScripts = (scripts != null ? new ArrayList<String>(scripts) : Collections.<String>emptyList());
}
/**
* Returns an unmodifiable list of shutdown scripts.
*/
public List<String> getShutdownScripts() {
return Collections.unmodifiableList(shutdownScripts);
return Collections.unmodifiableList(this.shutdownScripts);
}
}

View File

@@ -19,28 +19,32 @@ import org.springframework.cassandra.config.CassandraCqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.datastax.driver.core.Cluster;
/**
* Base class for Spring Cassandra configuration that can handle creating namespaces, execute arbitrary CQL on startup &
* shutdown, and optionally drop namespaces.
*
* Spring {@link @Configuration} class used to configure a Cassandra client application
* {@link com.datastax.driver.core.Session} connected to a Cassandra {@link com.datastax.driver.core.Cluster}.
*
* Enables a Cassandra Keyspace to be specified along with the ability to execute arbitrary CQL on startup
* as well as shutdown.
*
* @author Matthew T. Adams
* @author John Blum
* @see org.springframework.cassandra.config.java.AbstractClusterConfiguration
* @see org.springframework.context.annotation.Configuration
*/
@Configuration
public abstract class AbstractSessionConfiguration extends AbstractClusterConfiguration {
protected abstract String getKeyspaceName();
@Bean
public CassandraCqlSessionFactoryBean session() throws Exception {
Cluster cluster = cluster().getObject();
CassandraCqlSessionFactoryBean bean = new CassandraCqlSessionFactoryBean();
bean.setCluster(cluster);
bean.setCluster(cluster().getObject());
bean.setKeyspaceName(getKeyspaceName());
return bean;
}
protected abstract String getKeyspaceName();
}

View File

@@ -13,64 +13,44 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cassandra.core.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionUtils {
public abstract class CollectionUtils extends org.springframework.util.CollectionUtils {
@SuppressWarnings("unchecked")
public static <T> T[] toArray(Iterable<T> i) {
return (T[]) toList(i).toArray();
public static Object[] toArray(Iterable<?> iterable) {
return toList(iterable).toArray();
}
public static <T> List<T> toList(Iterable<T> i) {
public static <T> List<T> toList(T... elements) {
List<T> list = Collections.emptyList();
List<T> list = null;
if (i instanceof List) {
list = (List<T>) i;
} else {
list = new ArrayList<T>();
for (T t : i) {
list.add(t);
}
if (elements != null) {
list = new ArrayList<T>(elements.length);
Collections.addAll(list, elements);
}
return list;
}
public static List<?> toList(Object thing) {
List<Object> list = new ArrayList<Object>();
list.add(thing);
return list;
}
public static <T> List<T> toList(Iterable<T> iterable) {
if (!(iterable instanceof List)) {
List<T> list = new ArrayList<T>();
public static List<?> toList(Object thing1, Object thing2) {
List<Object> list = new ArrayList<Object>();
list.add(thing1);
list.add(thing2);
return list;
}
public static List<?> toList(Object thing1, Object thing2, Object thing3) {
List<Object> list = new ArrayList<Object>();
list.add(thing1);
list.add(thing2);
list.add(thing3);
return list;
}
public static List<?> toList(Object thing1, Object thing2, Object thing3, Object... rest) {
List<Object> list = new ArrayList<Object>();
list.add(thing1);
list.add(thing2);
list.add(thing3);
if (rest != null) {
for (Object thing : rest) {
list.add(thing);
if (iterable != null) {
for (T element : iterable) {
list.add(element);
}
}
iterable = list;
}
return list;
return (List<T>) iterable;
}
}

View File

@@ -0,0 +1,349 @@
/*
* Copyright 2013-2016 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 static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cassandra.core.CqlOperations;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Session;
/**
* The CassandraCqlSessionFactoryBeanUnitTests class is a test suite of test cases testing the contract
* and functionality of the {@link CassandraCqlSessionFactoryBean} class.
*
* @author John Blum
* @see org.junit.Rule
* @see org.junit.Test
* @see org.junit.rules.ExpectedException
* @see org.junit.runner.RunWith
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.runners.MockitoJUnitRunner
* @see org.springframework.cassandra.config.CassandraCqlSessionFactoryBean
* @since 1.5.0
*/
@RunWith(MockitoJUnitRunner.class)
public class CassandraCqlSessionFactoryBeanUnitTests {
@Rule
public ExpectedException exception = ExpectedException.none();
@Mock
private Cluster mockCluster;
@Mock
private Session mockSession;
private CassandraCqlSessionFactoryBean factoryBean;
protected <T> List<T> asList(T... array) {
return new ArrayList<T>(Arrays.asList(array));
}
protected void assertNonNullEmptyCollection(Collection<?> collection) {
assertThat(collection, is(notNullValue()));
assertThat(collection.isEmpty(), is(true));
}
@Before
public void setup() {
factoryBean = spy(new CassandraCqlSessionFactoryBean());
}
@Test
public void cassandraCqlSessionFactoryBeanIsSingleton() {
assertThat(factoryBean.isSingleton(), is(true));
}
@Test
public void objectTypeWhenSessionHasNotBeenInitializedIsSessionClass() {
assertThat(factoryBean.getObject(), is(nullValue()));
assertEquals(Session.class, factoryBean.<Session>getObjectType());
}
@Test
@SuppressWarnings("unchecked")
public void afterPropertiesSetInitializesSessionWithKeyspaceAndExecutesStartupScripts() throws Exception {
List<String> expectedStartupScripts = asList("/path/to/schema.cql", "/path/to/data.cql");
CqlOperations mockCqlOperations = mock(CqlOperations.class);
doReturn(mockSession).when(factoryBean).connect(eq("TestKeyspace"));
doReturn(mockCqlOperations).when(factoryBean).newCqlOperations(eq(mockSession));
factoryBean.setKeyspaceName("TestKeyspace");
factoryBean.setStartupScripts(expectedStartupScripts);
assertThat(factoryBean.getKeyspaceName(), is(equalTo("TestKeyspace")));
assertThat(factoryBean.getStartupScripts(), is(equalTo(expectedStartupScripts)));
factoryBean.afterPropertiesSet();
assertEquals(mockSession.getClass(), factoryBean.getObjectType());
assertThat(factoryBean.getObject(), is(equalTo(mockSession)));
assertThat(factoryBean.getSession(), is(equalTo(mockSession)));
InOrder inOrder = inOrder(factoryBean);
inOrder.verify(factoryBean, times(1)).connect(eq("TestKeyspace"));
inOrder.verify(factoryBean, times(1)).executeScripts(eq(expectedStartupScripts));
inOrder.verify(factoryBean, times(1)).newCqlOperations(eq(mockSession));
verify(mockCqlOperations, times(1)).execute(eq(expectedStartupScripts.get(0)));
verify(mockCqlOperations, times(1)).execute(eq(expectedStartupScripts.get(1)));
}
@Test
public void connectToSystemKeyspace() {
when(mockCluster.connect()).thenReturn(mockSession);
factoryBean.setCluster(mockCluster);
assertThat(factoryBean.connect(null), is(equalTo(mockSession)));
verify(mockCluster, times(1)).connect();
verify(mockCluster, never()).connect(anyString());
}
@Test
public void connectToTargetKeyspace() {
when(mockCluster.connect(eq("TestKeyspace"))).thenReturn(mockSession);
factoryBean.setCluster(mockCluster);
assertThat(factoryBean.connect("TestKeyspace"), is(equalTo(mockSession)));
verify(mockCluster, never()).connect();
verify(mockCluster, times(1)).connect(eq("TestKeyspace"));
}
@Test
@SuppressWarnings("unchecked")
public void destroySessionAndExecutesShutdownScripts() throws Exception {
List<String> expectedShutdownScripts = asList("/path/to/shutdown.cql");
CqlOperations mockCqlOperations = mock(CqlOperations.class);
doReturn(mockSession).when(factoryBean).getSession();
doReturn(mockCqlOperations).when(factoryBean).newCqlOperations(eq(mockSession));
factoryBean.setShutdownScripts(expectedShutdownScripts);
factoryBean.destroy();
InOrder inOrder = inOrder(factoryBean, mockSession);
inOrder.verify(factoryBean, times(1)).executeScripts(eq(expectedShutdownScripts));
verify(mockCqlOperations, times(1)).execute(eq(expectedShutdownScripts.get(0)));
inOrder.verify(mockSession, times(1)).close();
}
@Test
public void isConnectedWithNullSessionIsFalse() {
assertThat(factoryBean.getObject(), is(nullValue()));
assertThat(factoryBean.isConnected(), is(false));
}
@Test
public void isConnectedWithClosedSessionIsFalse() {
doReturn(mockSession).when(factoryBean).getObject();
when(mockSession.isClosed()).thenReturn(true);
assertThat(factoryBean.isConnected(), is(false));
verify(mockSession, times(1)).isClosed();
}
@Test
public void isConnectedWithOpenSessionIsTrue() {
doReturn(mockSession).when(factoryBean).getObject();
when(mockSession.isClosed()).thenReturn(false);
assertThat(factoryBean.isConnected(), is(true));
verify(mockSession, times(1)).isClosed();
}
@Test
public void setAndGetCluster() {
factoryBean.setCluster(mockCluster);
assertThat(factoryBean.getCluster(), is(equalTo(mockCluster)));
}
@Test
public void setClusterToNullThrowsIllegalArgumentException() {
exception.expect(IllegalArgumentException.class);
exception.expectCause(is(nullValue(Throwable.class)));
exception.expectMessage("Cluster must not be null");
factoryBean.setCluster(null);
}
@Test
public void getClusterWhenUninitializedThrowsIllegalStateException() {
exception.expect(IllegalStateException.class);
exception.expectCause(is(nullValue(Throwable.class)));
exception.expectMessage("Cluster was not properly initialized");
factoryBean.getCluster();
}
@Test
public void setAndGetKeyspaceName() {
assertThat(factoryBean.getKeyspaceName(), is(nullValue()));
factoryBean.setKeyspaceName("TEST");
assertThat(factoryBean.getKeyspaceName(), is(equalTo("TEST")));
factoryBean.setKeyspaceName(null);
assertThat(factoryBean.getKeyspaceName(), is(nullValue()));
}
@Test
public void getSessionWhenUninitializedThrowsIllegalStateException() {
exception.expect(IllegalStateException.class);
exception.expectCause(is(nullValue(Throwable.class)));
exception.expectMessage(is(equalTo("Session was not properly initialized")));
assertThat(factoryBean.getObject(), is(nullValue()));
factoryBean.getSession();
}
@Test
public void setAndGetStartupScripts() {
assertNonNullEmptyCollection(factoryBean.getStartupScripts());
List<String> expectedStartupScripts = asList("/path/to/schema.cql", "/path/to/data.cql");
factoryBean.setStartupScripts(expectedStartupScripts);
List<String> actualStartupScripts = factoryBean.getStartupScripts();
assertThat(actualStartupScripts, is(not(sameInstance(expectedStartupScripts))));
assertThat(actualStartupScripts, is(equalTo(expectedStartupScripts)));
factoryBean.setStartupScripts(null);
assertNonNullEmptyCollection(factoryBean.getStartupScripts());
}
@Test
public void startupScriptsAreImmutable() {
List<String> startupScripts = asList("/path/to/startup.cql");
factoryBean.setStartupScripts(startupScripts);
List<String> actualStartupScripts = factoryBean.getStartupScripts();
assertThat(actualStartupScripts, is(notNullValue()));
assertThat(actualStartupScripts, is(not(sameInstance(startupScripts))));
assertThat(actualStartupScripts, is(equalTo(startupScripts)));
startupScripts.add("/path/to/another.cql");
actualStartupScripts = factoryBean.getStartupScripts();
assertThat(actualStartupScripts, is(not(equalTo(startupScripts))));
assertThat(actualStartupScripts.size(), is(equalTo(1)));
assertThat(actualStartupScripts.get(0), is(equalTo(startupScripts.get(0))));
try {
exception.expect(UnsupportedOperationException.class);
actualStartupScripts.add("/path/to/yetAnother.cql");
}
finally {
assertThat(actualStartupScripts.size(), is(equalTo(1)));
}
}
@Test
public void setAndGetShutdownScripts() {
assertNonNullEmptyCollection(factoryBean.getShutdownScripts());
List<String> expectedShutdownScripts = asList("/path/to/backup.cql", "/path/to/dropTables.cql");
factoryBean.setShutdownScripts(expectedShutdownScripts);
List<String> actualShutdownScripts = factoryBean.getShutdownScripts();
assertThat(actualShutdownScripts, is(not(sameInstance(expectedShutdownScripts))));
assertThat(actualShutdownScripts, is(equalTo(expectedShutdownScripts)));
factoryBean.setShutdownScripts(null);
assertNonNullEmptyCollection(factoryBean.getShutdownScripts());
}
@Test
public void shutdownScriptsAreImmutable() {
List<String> shutdownScripts = asList("/path/to/shutdown.cql");
factoryBean.setShutdownScripts(shutdownScripts);
List<String> actualShutdownScripts = factoryBean.getShutdownScripts();
assertThat(actualShutdownScripts, is(notNullValue()));
assertThat(actualShutdownScripts, is(not(sameInstance(shutdownScripts))));
assertThat(actualShutdownScripts, is(equalTo(shutdownScripts)));
shutdownScripts.add("/path/to/corruptSession.cql");
actualShutdownScripts = factoryBean.getShutdownScripts();
assertThat(actualShutdownScripts, is(not(sameInstance(shutdownScripts))));
assertThat(actualShutdownScripts, is(not(equalTo(shutdownScripts))));
assertThat(actualShutdownScripts.size(), is(equalTo(1)));
try {
exception.expect(UnsupportedOperationException.class);
actualShutdownScripts.add("/path/to/blowUpCluster.cql");
}
finally {
assertThat(actualShutdownScripts.size(), is(equalTo(1)));
}
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2013-2016 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.core.util;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.junit.Test;
/**
* The CollectionUtilsUnitTests class is a test suite of test cases testing the contract and functionality
* of the {@link CollectionUtils} class.
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.cassandra.core.util.CollectionUtils
* @since 1.5.0
*/
public class CollectionUtilsUnitTests {
<T> List<T> asList(T... array) {
return Arrays.asList(array);
}
void assertNonNullEmptyArray(Object[] array) {
assertThat(array, is(notNullValue()));
assertThat(array.length, is(equalTo(0)));
}
void assertNonNullEmptyCollection(Collection<?> collection) {
assertThat(collection, is(notNullValue()));
assertThat(collection.isEmpty(), is(true));
}
<T> Iterable<T> newIterable(final T... elements) {
return new Iterable<T>() {
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
int index = 0;
@Override
public boolean hasNext() {
return (index < elements.length);
}
@Override
public T next() {
return elements[index++];
}
};
}
};
}
@Test
public void toArrayWithIterable() {
Object[] array = CollectionUtils.toArray(newIterable(1, 2, 3));
assertThat(array, is(notNullValue()));
assertThat(array.length, is(equalTo(3)));
for (int index = 0; index < array.length; index++) {
Object valueAtIndex = (index + 1);
assertThat(array[index], is(equalTo(valueAtIndex)));
}
}
@Test
public void toArrayWithEmptyIterable() {
assertNonNullEmptyArray(CollectionUtils.toArray(newIterable()));
}
@Test
public void toArrayWithNull() {
assertNonNullEmptyArray(CollectionUtils.toArray(null));
}
@Test
public void toListWithArray() {
List<String> list = CollectionUtils.toList("test", "testing", "tested");
assertThat(list, is(notNullValue()));
assertThat(list.size(), is(equalTo(3)));
assertThat(list.containsAll(asList("test", "testing", "tested")), is(true));
}
@Test
public void toListWithEmptyArray() {
assertNonNullEmptyCollection(CollectionUtils.toList());
}
@Test
public void toListWithNullArray() {
assertNonNullEmptyCollection(CollectionUtils.toList((Object[]) null));
}
@Test
@SuppressWarnings("unchecked")
public void toListWithIterable() {
List<Integer> list = CollectionUtils.toList(newIterable(1, 2, 3));
assertThat(list, is(notNullValue()));
assertThat(list.size(), is(equalTo(3)));
assertThat(list.containsAll(asList(1, 2, 3)), is(true));
}
@Test
public void toListWithList() {
List<Integer> expected = asList(1, 2, 3);
List<Integer> actual = CollectionUtils.toList(expected);
assertThat(actual, is(sameInstance(expected)));
}
@Test
public void toListWithNullIterable() {
assertNonNullEmptyCollection(CollectionUtils.toList((Iterable<?>) null));
}
}

View File

@@ -1,14 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-cassandra</artifactId>
<name>Spring Data Cassandra - Core</name>
<description>Cassandra Support for Spring Data</description>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-cassandra-parent</artifactId>
@@ -16,6 +12,12 @@
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-data-cassandra</artifactId>
<name>Spring Data Cassandra Core</name>
<description>Cassandra support for Spring Data</description>
<url>https://github.com/spring-projects/spring-data-cassandra/tree/master/spring-data-cassandra</url>
<properties>
<validation>1.0.0.GA</validation>
</properties>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors
* Copyright 2013-2016 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.
@@ -13,12 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.config;
import static org.springframework.cassandra.core.cql.CqlIdentifier.cqlId;
import java.util.Collection;
import org.springframework.cassandra.config.CassandraCqlSessionFactoryBean;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.core.CassandraAdminOperations;
import org.springframework.data.cassandra.core.CassandraAdminTemplate;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
@@ -26,96 +30,136 @@ import org.springframework.util.Assert;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.Metadata;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.TableMetadata;
import static org.springframework.cassandra.core.cql.CqlIdentifier.cqlId;
/**
* Factory to create and configure a Cassandra {@link com.datastax.driver.core.Session} with support
* for executing CQL and initializing the database schema (a.k.a. keyspace).
*
* @author Mathew Adams
* @author David Webb
* @author John Blum
* @see com.datastax.driver.core.KeyspaceMetadata
* @see com.datastax.driver.core.TableMetadata
*/
public class CassandraSessionFactoryBean extends CassandraCqlSessionFactoryBean {
protected SchemaAction schemaAction = SchemaAction.NONE;
protected CassandraAdminTemplate admin;
protected CassandraConverter converter;
protected CassandraMappingContext mappingContext;
protected static final boolean DEFAULT_CREATE_IF_NOT_EXISTS = false;
protected static final boolean DEFAULT_DROP_TABLES = false;
protected static final boolean DEFAULT_DROP_UNUSED_TABLES = false;
private CassandraAdminOperations admin;
private CassandraConverter converter;
private SchemaAction schemaAction = SchemaAction.NONE;
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
Assert.notNull(converter);
Assert.state(converter != null, "Converter must not be null");
admin = new CassandraAdminTemplate(session, converter);
admin = newCassandraAdminOperations(getObject(), converter);
performSchemaAction();
}
/* (non-Javadoc) */
CassandraAdminOperations newCassandraAdminOperations(Session session, CassandraConverter converter) {
return new CassandraAdminTemplate(session, converter);
}
/* (non-Javadoc) */
protected void performSchemaAction() {
boolean dropTables = false;
boolean dropUnused = false;
boolean dropTables = DEFAULT_DROP_TABLES;
boolean dropUnused = DEFAULT_DROP_UNUSED_TABLES;
boolean ifNotExists = DEFAULT_CREATE_IF_NOT_EXISTS;
switch (schemaAction) {
case NONE:
return;
case RECREATE_DROP_UNUSED:
dropUnused = true;
// don't break!
case RECREATE:
dropTables = true;
// don't break!
case CREATE_IF_NOT_EXISTS:
ifNotExists = SchemaAction.CREATE_IF_NOT_EXISTS.equals(schemaAction);
case CREATE:
createTables(dropTables, dropUnused);
createTables(dropTables, dropUnused, ifNotExists);
case NONE:
default:
// do nothing
}
}
protected void createTables(boolean dropTables, boolean dropUnused) {
/* (non-Javadoc) */
protected void createTables(boolean dropTables, boolean dropUnused, boolean ifNotExists) {
Metadata md = session.getCluster().getMetadata();
KeyspaceMetadata kmd = md.getKeyspace(keyspaceName);
// TODO: fix this with KeyspaceIdentifier
if (kmd == null) { // try lower-cased keyspace name
kmd = md.getKeyspace(keyspaceName.toLowerCase());
if (dropTables) {
dropTables(dropUnused);
}
if (kmd == null) {
throw new IllegalStateException(String.format("keyspace [%s] does not exist", keyspaceName));
}
for (TableMetadata table : kmd.getTables()) {
if (dropTables) {
if (dropUnused || mappingContext.usesTable(table)) {
admin.dropTable(cqlId(table.getName()));
}
}
}
Collection<? extends CassandraPersistentEntity<?>> entities = converter.getMappingContext()
.getNonPrimaryKeyEntities();
Collection<? extends CassandraPersistentEntity<?>> entities =
getConverter().getMappingContext().getNonPrimaryKeyEntities();
for (CassandraPersistentEntity<?> entity : entities) {
admin.createTable(false, entity.getTableName(), entity.getType(), null); // TODO: allow spec of table options
// TODO: pass specification of user configurable table options
getCassandraAdminOperations().createTable(ifNotExists, entity.getTableName(), entity.getType(), null);
}
}
public SchemaAction getSchemaAction() {
return schemaAction;
/* (non-Javadoc) */
@SuppressWarnings("all")
protected void dropTables(boolean dropUnused) {
String keyspaceName = getKeyspaceName();
Metadata clusterMetadata = getSession().getCluster().getMetadata();
KeyspaceMetadata keyspaceMetadata = clusterMetadata.getKeyspace(keyspaceName);
// TODO: fix this with KeyspaceIdentifier
keyspaceMetadata = (keyspaceMetadata != null ? keyspaceMetadata
: clusterMetadata.getKeyspace(keyspaceName.toLowerCase()));
Assert.state(keyspaceMetadata != null, String.format("keyspace [%s] does not exist", keyspaceName));
for (TableMetadata table : keyspaceMetadata.getTables()) {
if (dropUnused || getMappingContext().usesTable(table)) {
getCassandraAdminOperations().dropTable(cqlId(table.getName()));
}
}
}
/* (non-Javadoc) */
protected CassandraAdminOperations getCassandraAdminOperations() {
return this.admin;
}
/* (non-Javadoc) */
public void setConverter(CassandraConverter converter) {
Assert.notNull(converter, "CassandraConverter must not be null");
this.converter = converter;
}
/* (non-Javadoc) */
public CassandraConverter getConverter() {
return this.converter;
}
/* (non-Javadoc) */
protected CassandraMappingContext getMappingContext() {
return getConverter().getMappingContext();
}
/* (non-Javadoc) */
public void setSchemaAction(SchemaAction schemaAction) {
Assert.notNull(schemaAction);
Assert.notNull(schemaAction, "SchemaAction must not be null");
this.schemaAction = schemaAction;
}
public CassandraConverter getConverter() {
return converter;
}
public void setConverter(CassandraConverter converter) {
Assert.notNull(converter);
this.converter = converter;
this.mappingContext = converter.getMappingContext();
/* (non-Javadoc) */
public SchemaAction getSchemaAction() {
return schemaAction;
}
}

View File

@@ -13,12 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.config;
/**
* Enum identifying any schema actions to take at startup.
*
* @author Matthew T. Adams
* @author John Blum
*/
public enum SchemaAction {
@@ -32,6 +34,11 @@ public enum SchemaAction {
*/
CREATE,
/**
* Create each table as necessary. Avoid table creation if the table already exists.
*/
CREATE_IF_NOT_EXISTS,
/**
* Create each table as necessary, dropping the table first if it exists.
*/
@@ -44,11 +51,6 @@ public enum SchemaAction {
// TODO:
// /**
// * Validate that each required table and column exists. Fail if any required table or column does not exists.
// */
// VALIDATE,
//
// /**
// * Alter or create each table and column as necessary, leaving unused tables and columns untouched.
// */
// UPDATE,
@@ -56,5 +58,11 @@ public enum SchemaAction {
// /**
// * Alter or create each table and column as necessary, removing unused tables and columns.
// */
// UPDATE_DROP_UNUNSED;
// UPDATE_DROP_UNUNSED,
//
// /**
// * Validate that each required table and column exists. Fail if any required table or column does not exists.
// */
// VALIDATE
}

View File

@@ -19,15 +19,15 @@ import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.cassandra.config.java.AbstractClusterConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.CassandraSessionFactoryBean;
import org.springframework.data.cassandra.config.CassandraEntityClassScanner;
import org.springframework.data.cassandra.config.CassandraSessionFactoryBean;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.CassandraAdminOperations;
import org.springframework.data.cassandra.core.CassandraAdminTemplate;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.Table;
import org.springframework.data.mapping.context.MappingContext;
@@ -36,68 +36,43 @@ import org.springframework.data.mapping.context.MappingContext;
*
* @author Alex Shvid
* @author Matthew T. Adams
* @author John Blum
*/
@Configuration
public abstract class AbstractCassandraConfiguration extends AbstractClusterConfiguration implements
BeanClassLoaderAware {
protected abstract String getKeyspaceName();
public abstract class AbstractCassandraConfiguration extends AbstractClusterConfiguration
implements BeanClassLoaderAware {
protected ClassLoader beanClassLoader;
/**
* The {@link SchemaAction} to perform. Defaults to {@link SchemaAction#NONE}.
*/
public SchemaAction getSchemaAction() {
return SchemaAction.NONE;
}
/**
* The base packages to scan for entities annotated with {@link Table} annotations. By default, returns the package
* name of {@literal this} (<code>this.getClass().getPackage().getName()</code>). This method must never return null.
*/
public String[] getEntityBasePackages() {
return new String[] { getClass().getPackage().getName() };
}
@Bean
public CassandraSessionFactoryBean session() throws Exception {
CassandraSessionFactoryBean bean = new CassandraSessionFactoryBean();
CassandraSessionFactoryBean session = new CassandraSessionFactoryBean();
bean.setCluster(cluster().getObject());
bean.setConverter(cassandraConverter());
bean.setSchemaAction(getSchemaAction());
bean.setKeyspaceName(getKeyspaceName());
bean.setStartupScripts(getStartupScripts());
bean.setShutdownScripts(getShutdownScripts());
session.setCluster(cluster().getObject());
session.setConverter(cassandraConverter());
session.setKeyspaceName(getKeyspaceName());
session.setSchemaAction(getSchemaAction());
session.setStartupScripts(getStartupScripts());
session.setShutdownScripts(getShutdownScripts());
return bean;
}
/**
* Creates a {@link CassandraAdminTemplate}.
*
* @throws Exception
*/
@Bean
public CassandraAdminOperations cassandraTemplate() throws Exception {
return new CassandraAdminTemplate(session().getObject(), cassandraConverter());
return session;
}
/**
* Return the {@link MappingContext} instance to map Entities to properties.
*
*
* @throws ClassNotFoundException
*/
@Bean
public CassandraMappingContext cassandraMapping() throws ClassNotFoundException {
BasicCassandraMappingContext bean = new BasicCassandraMappingContext();
bean.setInitialEntitySet(CassandraEntityClassScanner.scan(getEntityBasePackages()));
bean.setBeanClassLoader(beanClassLoader);
BasicCassandraMappingContext mappingContext = new BasicCassandraMappingContext();
return bean;
mappingContext.setBeanClassLoader(beanClassLoader);
mappingContext.setInitialEntitySet(CassandraEntityClassScanner.scan(getEntityBasePackages()));
return mappingContext;
}
/**
@@ -108,8 +83,37 @@ public abstract class AbstractCassandraConfiguration extends AbstractClusterConf
return new MappingCassandraConverter(cassandraMapping());
}
/**
* Creates a {@link CassandraAdminTemplate}.
*
* @throws Exception if the {@link com.datastax.driver.core.Session} could not be obtained.
*/
@Bean
public CassandraAdminOperations cassandraTemplate() throws Exception {
return new CassandraAdminTemplate(session().getObject(), cassandraConverter());
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
/**
* Base packages to scan for entities annotated with {@link Table} annotations. By default, returns the package
* name of {@literal this} (<code>this.getClass().getPackage().getName()</code>).
*
* This method must never return null.
*/
public String[] getEntityBasePackages() {
return new String[] { getClass().getPackage().getName() };
}
protected abstract String getKeyspaceName();
/**
* The {@link SchemaAction} to perform at startup. Defaults to {@link SchemaAction#NONE}.
*/
public SchemaAction getSchemaAction() {
return SchemaAction.NONE;
}
}

View File

@@ -29,14 +29,12 @@ public class CassandraNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("repositories", new RepositoryBeanDefinitionParser(
new CassandraRepositoryConfigurationExtension()));
registerBeanDefinitionParser("cluster", new CassandraClusterParser());
registerBeanDefinitionParser("session", new CassandraSessionParser());
registerBeanDefinitionParser("template", new CassandraTemplateParser());
registerBeanDefinitionParser("converter", new CassandraMappingConverterParser());
registerBeanDefinitionParser("mapping", new CassandraMappingContextParser());
registerBeanDefinitionParser("repositories", new RepositoryBeanDefinitionParser(
new CassandraRepositoryConfigurationExtension()));
}
}

View File

@@ -94,12 +94,25 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation
setConverter(converter);
}
private static CassandraConverter getDefaultCassandraConverter() {
MappingCassandraConverter mappingCassandraConverter = new MappingCassandraConverter();
mappingCassandraConverter.afterPropertiesSet();
return mappingCassandraConverter;
}
/**
* Set the {@link CassandraConverter} used by this template to perform conversions.
*
* @param cassandraConverter Converter used to perform conversion of Cassandra data types to entity types.
* Must not be {@literal null}.
*/
public void setConverter(CassandraConverter cassandraConverter) {
Assert.notNull(cassandraConverter);
Assert.notNull(cassandraConverter, "CassandraConverter must not be null");
this.cassandraConverter = cassandraConverter;
mappingContext = cassandraConverter.getMappingContext();
this.mappingContext = cassandraConverter.getMappingContext();
}
@Override
@@ -838,10 +851,6 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation
/**
* Generates a Query Object for an insert
*
* @param tableName
* @param objectToSave
* @param entity
* @param optionsByName
* @return The Query object to run with session.execute();
*/
public static Insert createInsertQuery(String tableName, Object objectToSave, WriteOptions options,
@@ -866,10 +875,6 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation
/**
* Generates a Query Object for an Update
*
* @param tableName
* @param objectToSave
* @param entity
* @param optionsByName
* @return The Query object to run with session.execute();
*/
public static Update createUpdateQuery(String tableName, Object objectToSave, WriteOptions options,
@@ -894,10 +899,6 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation
/**
* Generates a Batch Object for multiple Updates
*
* @param tableName
* @param objectsToSave
* @param entity
* @param optionsByName
* @return The Query object to run with session.execute();
*/
public static <T> Batch createUpdateBatchQuery(String tableName, List<T> objectsToSave, WriteOptions options,
@@ -917,10 +918,6 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation
/**
* Generates a Batch Object for multiple inserts
*
* @param tableName
* @param entities
* @param entity
* @param optionsByName
* @return The Query object to run with session.execute();
*/
public static <T> Batch createInsertBatchQuery(String tableName, List<T> entities, WriteOptions options,
@@ -939,12 +936,6 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation
/**
* Create a Delete Query Object from an annotated POJO
*
* @param tableName
* @param object
* @param entity
* @param optionsByName
* @return
*/
public static Delete createDeleteQuery(String tableName, Object object, QueryOptions options,
EntityWriter<Object, Object> entityWriter) {
@@ -959,12 +950,6 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation
/**
* Create a Batch Query object for multiple deletes.
*
* @param tableName
* @param entities
* @param entity
* @param optionsByName
* @return
*/
public static <T> Batch createDeleteBatchQuery(String tableName, List<T> entities, QueryOptions options,
EntityWriter<Object, Object> entityWriter) {

View File

@@ -153,7 +153,7 @@ public class BasicCassandraMappingContext extends
@Override
public boolean usesTable(TableMetadata table) {
return entitySetsByTableName.containsKey(table.getName());
return entitySetsByTableName.containsKey(cqlId(table.getName()));
}
@Override

View File

@@ -0,0 +1,342 @@
/*
* Copyright 2013-2016 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.config;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.springframework.data.cassandra.config.CassandraSessionFactoryBean.DEFAULT_CREATE_IF_NOT_EXISTS;
import static org.springframework.data.cassandra.config.CassandraSessionFactoryBean.DEFAULT_DROP_TABLES;
import static org.springframework.data.cassandra.config.CassandraSessionFactoryBean.DEFAULT_DROP_UNUSED_TABLES;
import java.util.Collections;
import java.util.Map;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.core.CassandraAdminOperations;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.Metadata;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.TableMetadata;
/**
* The CassandraSessionFactoryBeanUnitTests class is a test suite of test cases testing the contract and functionality
* of the {@link CassandraSessionFactoryBean} class.
*
* @author John Blum
* @see org.junit.Rule
* @see org.junit.Test
* @see org.junit.rules.ExpectedException
* @see org.junit.runner.RunWith
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.runners.MockitoJUnitRunner
* @see org.springframework.data.cassandra.config.CassandraSessionFactoryBean
* @since 1.5.0
*/
@RunWith(MockitoJUnitRunner.class)
public class CassandraSessionFactoryBeanUnitTests {
@Rule
public ExpectedException exception = ExpectedException.none();
@Mock
private CassandraConverter mockConverter;
@Mock
private Cluster mockCluster;
@Mock
private Session mockSession;
private CassandraSessionFactoryBean factoryBean;
@Before
public void setup() {
when(mockCluster.connect()).thenReturn(mockSession);
when(mockSession.getCluster()).thenReturn(mockCluster);
factoryBean = spy(new CassandraSessionFactoryBean());
factoryBean.setCluster(mockCluster);
}
protected CqlIdentifier newCqlIdentifier(String id) {
return new CqlIdentifier(id, false);
}
@Test
public void afterPropertiesSetPerformsSchemaAction() throws Exception {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
assertThat(factoryBean.getSchemaAction(), is(equalTo(SchemaAction.RECREATE)));
return null;
}
}).when(factoryBean).performSchemaAction();
factoryBean.setConverter(mockConverter);
factoryBean.setSchemaAction(SchemaAction.RECREATE);
assertThat(factoryBean.getConverter(), is(equalTo(mockConverter)));
assertThat(factoryBean.getSchemaAction(), is(equalTo(SchemaAction.RECREATE)));
factoryBean.afterPropertiesSet();
assertThat(factoryBean.getCassandraAdminOperations(), is(notNullValue(CassandraAdminOperations.class)));
assertThat(factoryBean.getObject(), is(equalTo(mockSession)));
verify(factoryBean, times(1)).performSchemaAction();
}
@Test
public void afterPropertiesSetThrowsIllegalStateExceptionWhenConverterIsNull() throws Exception {
exception.expect(IllegalStateException.class);
exception.expectCause(is(nullValue(Throwable.class)));
exception.expectMessage("Converter must not be null");
factoryBean.setCluster(mockCluster);
factoryBean.afterPropertiesSet();
}
protected void performSchemaActionCallsCreateTableWithArgumentsMatchingTheSchemaAction(SchemaAction schemaAction,
final boolean dropTables, final boolean dropUnused, final boolean ifNotExists) {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
assertThat(invocationOnMock.getArgumentAt(0, Boolean.class), is(equalTo(dropTables)));
assertThat(invocationOnMock.getArgumentAt(1, Boolean.class), is(equalTo(dropUnused)));
assertThat(invocationOnMock.getArgumentAt(2, Boolean.class), is(equalTo(ifNotExists)));
return null;
}
}).when(factoryBean).createTables(anyBoolean(), anyBoolean(), anyBoolean());
factoryBean.setSchemaAction(schemaAction);
assertThat(factoryBean.getSchemaAction(), is(equalTo(schemaAction)));
factoryBean.performSchemaAction();
verify(factoryBean, times(1)).createTables(eq(dropTables), eq(dropUnused), eq(ifNotExists));
}
@Test
public void performsSchemaActionCreatesTablesWithDefaults() {
performSchemaActionCallsCreateTableWithArgumentsMatchingTheSchemaAction(SchemaAction.CREATE,
DEFAULT_DROP_TABLES, DEFAULT_DROP_UNUSED_TABLES, DEFAULT_CREATE_IF_NOT_EXISTS);
}
@Test
public void performsSchemaActionCreatesTablesIfNotExists() {
performSchemaActionCallsCreateTableWithArgumentsMatchingTheSchemaAction(SchemaAction.CREATE_IF_NOT_EXISTS,
DEFAULT_DROP_TABLES, DEFAULT_DROP_UNUSED_TABLES, true);
}
@Test
public void performsSchemaActionRecreatesTables() {
performSchemaActionCallsCreateTableWithArgumentsMatchingTheSchemaAction(SchemaAction.RECREATE, true,
DEFAULT_DROP_UNUSED_TABLES, DEFAULT_CREATE_IF_NOT_EXISTS);
}
@Test
public void performsSchemaActionRecreatesAndDropsUnusedTables() {
performSchemaActionCallsCreateTableWithArgumentsMatchingTheSchemaAction(SchemaAction.RECREATE_DROP_UNUSED,
true, true, DEFAULT_CREATE_IF_NOT_EXISTS);
}
@Test
public void performsSchemaActionDoesNotCallCreateTablesWhenSchemaActionIsNone() {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
fail("'createTables(..)' should not have been called");
return null;
}
}).when(factoryBean).createTables(anyBoolean(), anyBoolean(), anyBoolean());
factoryBean.setSchemaAction(SchemaAction.NONE);
assertThat(factoryBean.getSchemaAction(), is(equalTo(SchemaAction.NONE)));
factoryBean.performSchemaAction();
verify(factoryBean, never()).createTables(anyBoolean(), anyBoolean(), anyBoolean());
}
@Test
@SuppressWarnings("unchecked")
public void createsTableForEntity() throws Exception {
Metadata mockMetadata = mock(Metadata.class);
KeyspaceMetadata mockKeyspaceMetadata = mock(KeyspaceMetadata.class);
CassandraMappingContext mockMappingContext = mock(CassandraMappingContext.class);
CassandraPersistentEntity<Person> mockPersistentEntity = mock(CassandraPersistentEntity.class);
CassandraAdminOperations mockCassandraAdminOperations = mock(CassandraAdminOperations.class);
doReturn(mockCassandraAdminOperations).when(factoryBean).getCassandraAdminOperations();
doReturn(mockSession).when(factoryBean).getObject();
when(mockCluster.getMetadata()).thenReturn(mockMetadata);
when(mockMetadata.getKeyspace(eq("TestKeyspace"))).thenReturn(mockKeyspaceMetadata);
when(mockKeyspaceMetadata.getTables()).thenReturn(Collections.<TableMetadata>emptyList());
when(mockConverter.getMappingContext()).thenReturn(mockMappingContext);
when(mockMappingContext.getNonPrimaryKeyEntities()).thenReturn(
Collections.<CassandraPersistentEntity<?>>singletonList(mockPersistentEntity));
when(mockPersistentEntity.getTableName()).thenReturn(newCqlIdentifier("TestTable"));
when(mockPersistentEntity.getType()).thenReturn(Person.class);
factoryBean.setConverter(mockConverter);
factoryBean.setKeyspaceName("TestKeyspace");
assertThat(factoryBean.getConverter(), is(equalTo(mockConverter)));
factoryBean.createTables(true, false, false);
verify(mockSession, times(1)).getCluster();
verify(mockCluster, times(1)).getMetadata();
verify(mockMetadata, times(1)).getKeyspace(eq("TestKeyspace"));
verify(mockKeyspaceMetadata, times(1)).getTables();
verify(mockConverter, times(1)).getMappingContext();
verify(mockMappingContext, times(1)).getNonPrimaryKeyEntities();
verify(mockPersistentEntity, times(1)).getTableName();
verify(mockPersistentEntity, times(1)).getType();
verify(mockCassandraAdminOperations, times(1)).createTable(eq(false), eq(newCqlIdentifier("TestTable")),
eq(Person.class), isNull(Map.class));
}
@Test
@SuppressWarnings("unchecked")
public void createTableForEntityIfNotExists() {
CassandraMappingContext mockMappingContext = mock(CassandraMappingContext.class);
CassandraPersistentEntity<Person> mockPersistentEntity = mock(CassandraPersistentEntity.class);
CassandraAdminOperations mockCassandraAdminOperations = mock(CassandraAdminOperations.class);
doReturn(mockCassandraAdminOperations).when(factoryBean).getCassandraAdminOperations();
doReturn(mockSession).when(factoryBean).getObject();
when(mockConverter.getMappingContext()).thenReturn(mockMappingContext);
when(mockMappingContext.getNonPrimaryKeyEntities()).thenReturn(
Collections.<CassandraPersistentEntity<?>>singletonList(mockPersistentEntity));
when(mockPersistentEntity.getTableName()).thenReturn(newCqlIdentifier("TestTable"));
when(mockPersistentEntity.getType()).thenReturn(Person.class);
factoryBean.setConverter(mockConverter);
factoryBean.setKeyspaceName("TestKeyspace");
assertThat(factoryBean.getConverter(), is(equalTo(mockConverter)));
factoryBean.createTables(false, false, true);
verify(mockSession, never()).getCluster();
verify(mockCluster, never()).getMetadata();
verify(mockConverter, times(1)).getMappingContext();
verify(mockMappingContext, times(1)).getNonPrimaryKeyEntities();
verify(mockPersistentEntity, times(1)).getTableName();
verify(mockPersistentEntity, times(1)).getType();
verify(mockCassandraAdminOperations, times(1)).createTable(eq(true), eq(newCqlIdentifier("TestTable")),
eq(Person.class), isNull(Map.class));
}
@Test
public void createTableThrowsIllegalStateExceptionWhenKeyspaceNotFound() {
Metadata mockMetadata = mock(Metadata.class);
doReturn(mockSession).when(factoryBean).getObject();
when(mockCluster.getMetadata()).thenReturn(mockMetadata);
when(mockMetadata.getKeyspace(anyString())).thenReturn(null);
exception.expect(IllegalStateException.class);
exception.expectCause(is(nullValue(Throwable.class)));
exception.expectMessage("keyspace [TestKeyspace] does not exist");
factoryBean.setKeyspaceName("TestKeyspace");
factoryBean.createTables(true, false, true);
verify(mockSession, times(1)).getCluster();
verify(mockCluster, times(1)).getMetadata();
verify(mockMetadata, times(1)).getKeyspace(eq("TestKeyspace"));
verify(mockMetadata, times(1)).getKeyspace(eq("testkeyspace"));
}
// TODO: add more createTable tests covering drop tables, etc
@Test
public void setAndGetConverter() {
assertThat(factoryBean.getConverter(), is(nullValue()));
factoryBean.setConverter(mockConverter);
assertThat(factoryBean.getConverter(), is(equalTo(mockConverter)));
verifyZeroInteractions(mockConverter);
}
@Test
public void setConverterToNull() {
exception.expect(IllegalArgumentException.class);
exception.expectCause(is(nullValue(Throwable.class)));
exception.expectMessage("CassandraConverter must not be null");
factoryBean.setConverter(null);
}
@Test
public void setAndGetSchemaAction() {
assertThat(factoryBean.getSchemaAction(), is(equalTo(SchemaAction.NONE)));
factoryBean.setSchemaAction(SchemaAction.CREATE);
assertThat(factoryBean.getSchemaAction(), is(equalTo(SchemaAction.CREATE)));
factoryBean.setSchemaAction(SchemaAction.NONE);
assertThat(factoryBean.getSchemaAction(), is(equalTo(SchemaAction.NONE)));
}
@Test
public void setSchemaActionToNullThrowsIllegalArgumentException() {
exception.expect(IllegalArgumentException.class);
exception.expectCause(is(nullValue(Throwable.class)));
exception.expectMessage("SchemaAction must not be null");
factoryBean.setSchemaAction(null);
}
static class Person {
}
}

View File

@@ -1,11 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cass="http://www.springframework.org/schema/data/cassandra"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra-1.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
xmlns:cass="http://www.springframework.org/schema/data/cassandra"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
<bean name="randomKeyspaceName" class="java.lang.String">
<constructor-arg value="#{'ks' + T(java.util.UUID).randomUUID().toString().replaceAll('-', '')}"/>
@@ -16,6 +18,6 @@
<cass:converter mapping-ref="cassandraMapping"/>
<cass:template />
<cass:template/>
</beans>