diff --git a/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java b/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java index 1a001ef06..f8912f326 100644 --- a/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java +++ b/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java @@ -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 ITERABLE_CLASSES; + static { + Set iterableClasses = new HashSet(); + 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, 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 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 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() { + + 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() { + + 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 + + } + } diff --git a/src/main/java/org/springframework/data/cassandra/core/SessionCallback.java b/src/main/java/org/springframework/data/cassandra/core/SessionCallback.java index 01345dd33..ab0c54441 100644 --- a/src/main/java/org/springframework/data/cassandra/core/SessionCallback.java +++ b/src/main/java/org/springframework/data/cassandra/core/SessionCallback.java @@ -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 + */ public interface SessionCallback { + /** + * Perform the operation in the given Session + * + * @param s + * @return + * @throws DataAccessException + */ T doInSession(Session s) throws DataAccessException; + } diff --git a/src/main/java/org/springframework/data/cassandra/util/CQLUtils.java b/src/main/java/org/springframework/data/cassandra/util/CQLUtils.java index 7146a4987..213dddf2a 100644 --- a/src/main/java/org/springframework/data/cassandra/util/CQLUtils.java +++ b/src/main/java/org/springframework/data/cassandra/util/CQLUtils.java @@ -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() { + 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 cols = new ArrayList(); + + entity.doWithProperties(new PropertyHandler() { + 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()) { diff --git a/src/test/java/org/springframework/data/cassandra/template/CassandraOperationsTest.java b/src/test/java/org/springframework/data/cassandra/template/CassandraOperationsTest.java index 4a8d011cb..81818c2bb 100644 --- a/src/test/java/org/springframework/data/cassandra/template/CassandraOperationsTest.java +++ b/src/test/java/org/springframework/data/cassandra/template/CassandraOperationsTest.java @@ -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();