IN PROGRESS - issue DATACASS-32: Implement the TemplateAPI for CQL

https://jira.springsource.org/browse/DATACASS-32

Completed insert and createTable template calls.
This commit is contained in:
dwebb
2013-11-12 00:58:37 -05:00
parent 16d931ba5c
commit 9845659b89
4 changed files with 356 additions and 49 deletions

View File

@@ -16,10 +16,15 @@
package org.springframework.data.cassandra.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
@@ -27,13 +32,16 @@ import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.util.CQLUtils;
import org.springframework.data.cassandra.vo.RingMember;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import com.datastax.driver.core.Host;
import com.datastax.driver.core.Metadata;
import com.datastax.driver.core.Query;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
@@ -43,12 +51,28 @@ import com.datastax.driver.core.exceptions.NoHostAvailableException;
* @author Alex Shvid
*/
public class CassandraTemplate implements CassandraOperations {
private static Logger log = LoggerFactory.getLogger(CassandraTemplate.class);
private static final Collection<String> ITERABLE_CLASSES;
static {
Set<String> iterableClasses = new HashSet<String>();
iterableClasses.add(List.class.getName());
iterableClasses.add(Collection.class.getName());
iterableClasses.add(Iterator.class.getName());
ITERABLE_CLASSES = Collections.unmodifiableCollection(iterableClasses);
}
private final Keyspace keyspace;
private final Session session;
private final CassandraConverter cassandraConverter;
private final MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext;
private final PersistenceExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator();
private ClassLoader beanClassLoader;
/**
* Constructor used for a basic template configuration
@@ -56,11 +80,19 @@ public class CassandraTemplate implements CassandraOperations {
* @param keyspace must not be {@literal null}.
*/
public CassandraTemplate(Keyspace keyspace) {
this.keyspace = keyspace;
this.session = keyspace.getSession();
this.cassandraConverter = keyspace.getCassandraConverter();
this.mappingContext = this.cassandraConverter.getMappingContext();
}
/**
* @param classLoader
*/
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#describeRing()
*/
@@ -139,55 +171,6 @@ public class CassandraTemplate implements CassandraOperations {
}
public void insert(Object entity) {
// TODO Auto-generated method stub
}
public void insert(Object entity, String tableName) {
// TODO Auto-generated method stub
}
public void remove(Object object) {
// TODO Auto-generated method stub
}
public void remove(Object object, String tableName) {
// TODO Auto-generated method stub
}
public void createTable(Class<?> entityClass) {
// TODO Auto-generated method stub
}
public void createTable(Class<?> entityClass, String tableName) {
// TODO Auto-generated method stub
}
public void alterTable(Class<?> entityClass) {
// TODO Auto-generated method stub
}
public void alterTable(Class<?> entityClass, String tableName) {
// TODO Auto-generated method stub
}
public void dropTable(Class<?> entityClass) {
// TODO Auto-generated method stub
}
public void dropTable(String tableName) {
// TODO Auto-generated method stub
}
public CassandraConverter getConverter() {
return cassandraConverter;
@@ -265,6 +248,22 @@ public class CassandraTemplate implements CassandraOperations {
}
}
/**
* @param obj
* @return
*/
private <T> String determineTableName(T obj) {
if (null != obj) {
return determineTableName(obj.getClass());
}
return null;
}
/**
* @param entityClass
* @return
*/
String determineTableName(Class<?> entityClass) {
if (entityClass == null) {
@@ -286,6 +285,45 @@ public class CassandraTemplate implements CassandraOperations {
return resolved == null ? ex : resolved;
}
/**
* Insert a row into a Cassandra ColumnFamily
*
* @param tableName
* @param objectToSave
* @throws LinkageError
* @throws ClassNotFoundException
*/
protected <T> T doInsert(final String tableName, final T objectToSave) {
try {
final String entityClassName = objectToSave.getClass().getName();
final Class<?> entityClass = ClassUtils.forName(entityClassName, this.beanClassLoader);
final CassandraPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
final String useTableName = tableName != null ? tableName : entity.getTable();
return execute(new SessionCallback<T>() {
public T doInSession(Session s) throws DataAccessException {
Query q = CQLUtils.toInsertQuery(keyspace.getKeyspace(), useTableName, entity, objectToSave);
log.info(q.toString());
ResultSet rs = s.execute(q);
return null;
}
});
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (LinkageError e) {
e.printStackTrace();
} finally {}
return objectToSave;
}
/**
* Execute a command at the Session Level
@@ -306,4 +344,139 @@ public class CassandraTemplate implements CassandraOperations {
}
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object)
*/
public void insert(Object objectToSave) {
ensureNotIterable(objectToSave);
insert(objectToSave, determineTableName(objectToSave));
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object, java.lang.String)
*/
public void insert(Object objectToSave, String tableName) {
ensureNotIterable(objectToSave);
doInsert(tableName, objectToSave);
}
/**
* Verify the object is not an iterable type
* @param o
*/
protected void ensureNotIterable(Object o) {
if (null != o) {
if (o.getClass().isArray() || ITERABLE_CLASSES.contains(o.getClass().getName())) {
throw new IllegalArgumentException("Cannot use a collection here.");
}
}
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#remove(java.lang.Object)
*/
@Override
public void remove(Object object) {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#remove(java.lang.Object, java.lang.String)
*/
@Override
public void remove(Object object, String tableName) {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#createTable(java.lang.Class)
*/
@Override
public void createTable(Class<?> entityClass) {
try {
final CassandraPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
final String useTableName = entity.getTable();
createTable(entityClass, useTableName);
} catch (LinkageError e) {
e.printStackTrace();
} finally {}
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#createTable(java.lang.Class, java.lang.String)
*/
@Override
public void createTable(Class<?> entityClass, final String tableName) {
try {
final CassandraPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
execute(new SessionCallback<Object>() {
public Object doInSession(Session s) throws DataAccessException {
String cql = CQLUtils.createTable(tableName, entity);
log.info("CREATE TABLE CQL -> " + cql);
s.execute(cql);
return null;
}
});
} catch (LinkageError e) {
e.printStackTrace();
} finally {}
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#alterTable(java.lang.Class)
*/
@Override
public void alterTable(Class<?> entityClass) {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#alterTable(java.lang.Class, java.lang.String)
*/
@Override
public void alterTable(Class<?> entityClass, String tableName) {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#dropTable(java.lang.Class)
*/
@Override
public void dropTable(Class<?> entityClass) {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#dropTable(java.lang.String)
*/
@Override
public void dropTable(String tableName) {
// TODO Auto-generated method stub
}
}

View File

@@ -19,7 +19,22 @@ import org.springframework.dao.DataAccessException;
import com.datastax.driver.core.Session;
/**
* Interface for operations on a Cassnadra Session.
*
* @author David Webb (dwebb@brightmove.com)
*
* @param <T>
*/
public interface SessionCallback<T> {
/**
* Perform the operation in the given Session
*
* @param s
* @return
* @throws DataAccessException
*/
T doInSession(Session s) throws DataAccessException;
}

View File

@@ -1,8 +1,11 @@
package org.springframework.data.cassandra.util;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.mapping.CassandraPersistentProperty;
@@ -10,10 +13,15 @@ import org.springframework.data.mapping.PropertyHandler;
import com.datastax.driver.core.ColumnMetadata;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.Query;
import com.datastax.driver.core.TableMetadata;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
public abstract class CQLUtils {
private static Logger log = LoggerFactory.getLogger(CQLUtils.class);
public static String createTable(String tableName, final CassandraPersistentEntity<?> entity) {
@@ -154,6 +162,85 @@ public abstract class CQLUtils {
return result;
}
public static Query toInsertQuery(String keyspaceName, String tableName, final CassandraPersistentEntity<?> entity, final Object objectToSave) {
final Insert q = QueryBuilder.insertInto(keyspaceName, tableName);
entity.doWithProperties(new PropertyHandler<CassandraPersistentProperty>() {
public void doWithPersistentProperty(CassandraPersistentProperty prop) {
/*
* See if the object has a value for that column, and if so, add it to the Query
*/
try {
Object o = (String)prop.getGetter().invoke(objectToSave, new Object[0]);
log.info("Getter Invoke [" + prop.getColumnName() + " => " + o);
if (o != null) {
q.value(prop.getColumnName(), o);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
return q;
}
/**
* Generate the CQL for insert
*
* @param tableName
* @param entity
* @return
*/
public static String toInsertCQL(String tableName, final CassandraPersistentEntity<?> entity) {
final StringBuilder str = new StringBuilder();
str.append("INSERT INTO ");
str.append(tableName);
str.append(" (");
final List<String> cols = new ArrayList<String>();
entity.doWithProperties(new PropertyHandler<CassandraPersistentProperty>() {
public void doWithPersistentProperty(CassandraPersistentProperty prop) {
if (str.charAt(str.length()-1) != '(') {
str.append(", ");
}
String columnName = prop.getColumnName();
cols.add(columnName);
str.append(columnName);
}
});
str.append(") VALUES (");
for (int i = 0; i < cols.size(); i++) {
if (i > 0) {
str.append(", ");
}
str.append("?");
}
str.append(")");
return str.toString();
}
public static String toCQL(DataType dataType) {
if (dataType.getTypeArguments().isEmpty()) {

View File

@@ -18,6 +18,7 @@ import org.apache.thrift.transport.TTransportException;
import org.cassandraunit.utils.EmbeddedCassandraServerHelper;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -26,6 +27,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.cassandra.config.TestConfig;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.data.cassandra.test.User;
import org.springframework.data.cassandra.vo.RingMember;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -53,6 +55,24 @@ public class CassandraOperationsTest {
throws IOException, TTransportException, ConfigurationException, InterruptedException {
EmbeddedCassandraServerHelper.startEmbeddedCassandra("cassandra.yaml");
}
@Before
public void setupKeyspace() {
log.info("Creating Keyspace...");
cassandraTemplate.executeQuery("CREATE KEYSPACE test WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };");
log.info("Using Keyspace...");
cassandraTemplate.executeQuery("use test;");
log.info("Creating Table...");
cassandraTemplate.createTable(User.class);
}
@Test
public void ringTest() {
@@ -69,6 +89,18 @@ public class CassandraOperationsTest {
}
}
@Test
public void insertTest() {
User u = new User();
u.setUsername("cassandra");
u.setFirstName("Apache");
u.setLastName("Cassnadra");
cassandraTemplate.insert(u, "users");
}
@After
public void clearCassandra() {
EmbeddedCassandraServerHelper.cleanEmbeddedCassandra();