I have been playing around with
Cassandra lately using its Java (Thrift) client API. In order to map different classes (entities) to a list of ColumnOrSuperColumns, I have created a generic entity transformer.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.apache.cassandra.thrift.Mutation;
public abstract class CassandraEntityTransformer<T> {
public static final String CHARSET = "UTF-8";
public abstract List<ColumnOrSuperColumn> getColumns(T entity) throws Exception;
public abstract T getEntity(List<ColumnOrSuperColumn> columns) throws Exception;
public List<Mutation> listMutations(T oldEntity, T newEntity) throws Exception {
List<Mutation> mutations = new ArrayList<Mutation>();
List<ColumnOrSuperColumn> oldColumns = getColumns(oldEntity);
List<ColumnOrSuperColumn> newColumns = getColumns(newEntity);
if(oldColumns.size()!=newColumns.size())
throw new IllegalArgumentException("incompatible entities");
for(int i=0; i<newColumns.size(); i++) {
if(!Arrays.equals(newColumns.get(i).getColumn().getValue(),
oldColumns.get(i).getColumn().getValue())) {
mutations.add(
new Mutation().setColumn_or_supercolumn(newColumns.get(i)));
}
}
return mutations;
}
protected ColumnOrSuperColumn createColumn(Column column) {
ColumnOrSuperColumn c = new ColumnOrSuperColumn();
c.setColumn(column);
return c;
}
}
Subclasses of this class are responsible for providing the columns of concrete entities, like for the token entity in the following example. I found this quite handy, especially as getMutations works out of the box for all entities.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
public class CassandraTokenTransformer extends CassandraEntityTransformer<Token> {
private static final String VALUE_COLUMN_NAME = "value";
private static final String EMAIL_COLUMN_NAME = "email";
@Override
public List<ColumnOrSuperColumn> getColumns(Token token) throws Exception {
List<ColumnOrSuperColumn> columns = new ArrayList<ColumnOrSuperColumn>();
long timestamp = System.currentTimeMillis();
// the value column.
columns.add(createColumn(
new Column(VALUE_COLUMN_NAME.getBytes(CHARSET),
token.getValue().getBytes(CHARSET), timestamp)));
// the email column.
columns.add(createColumn(
new Column(EMAIL_COLUMN_NAME.getBytes(CHARSET),
token.getEmail().getBytes(CHARSET), timestamp)));
return columns;
}
@Override
public Token getEntity(List<ColumnOrSuperColumn> columns) {
Token token = null;
Map<String, String> fields = new HashMap<String, String>();
for (ColumnOrSuperColumn column : columns) {
fields.put(new String(column.column.name),
new String(column.column.value));
}
if(fields.containsKey(VALUE_COLUMN_NAME))
token = new Token(fields.get(VALUE_COLUMN_NAME),
fields.get(EMAIL_COLUMN_NAME));
return token;
}
}
Alternatives would have been to use an annotation based mapping mechanism for entities to Cassandra columns, or a JSON based API. Maybe they are out there, and I have just missed them?