
me.prettyprint.hom.HectorObjectMapper Maven / Gradle / Ivy
package me.prettyprint.hom;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import javax.persistence.DiscriminatorType;
import javax.persistence.Id;
import me.prettyprint.cassandra.serializers.BooleanSerializer;
import me.prettyprint.cassandra.serializers.BytesArraySerializer;
import me.prettyprint.cassandra.serializers.DateSerializer;
import me.prettyprint.cassandra.serializers.DoubleSerializer;
import me.prettyprint.cassandra.serializers.IntegerSerializer;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.ObjectSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.serializers.UUIDSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.Serializer;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.QueryResult;
import me.prettyprint.hector.api.query.SliceQuery;
import me.prettyprint.hom.annotations.AnonymousPropertyCollectionGetter;
import me.prettyprint.hom.annotations.Column;
import me.prettyprint.hom.cache.HectorObjectMapperException;
import me.prettyprint.hom.converters.Converter;
import me.prettyprint.hom.converters.DefaultConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Maps a slice of HColumn
s to an object's
* properties. See {@link #createObject(Object, ColumnSlice)} for more details.
*
* As mentioned above all column names must be String
s - doesn't
* really make sense to have other types when mapping to object properties.
*
* @param
* Type of object mapping to cassandra row
*
* @author Todd Burruss
*/
public class HectorObjectMapper {
private static Logger logger = LoggerFactory.getLogger(HectorObjectMapper.class);
private static final int MAX_NUM_COLUMNS = 100;
private int maxNumColumns = MAX_NUM_COLUMNS;
private ClassCacheMgr cacheMgr;
private KeyConcatenationStrategy keyConcatStrategy = new KeyConcatenationDelimiterStrategyImpl();
private CollectionMapperHelper collMapperHelper = new CollectionMapperHelper();
private ReflectionHelper reflectionHelper = new ReflectionHelper();
public HectorObjectMapper(ClassCacheMgr cacheMgr) {
this.cacheMgr = cacheMgr;
}
/**
* Retrieve columns from cassandra keyspace and column family, instantiate a
* new object of required type, and then map them to the object's properties.
*
* @param
*
* @param keyspace
* @param colFamName
* @param pkObj
* @return
*/
public T getObject(Keyspace keyspace, String colFamName, Object pkObj) {
if (null == pkObj) {
throw new IllegalArgumentException("object ID cannot be null or empty");
}
CFMappingDef cfMapDef = cacheMgr.getCfMapDef(colFamName, true);
byte[] colFamKey = generateColumnFamilyKeyFromPkObj(cfMapDef, pkObj);
SliceQuery q = HFactory.createSliceQuery(keyspace,
BytesArraySerializer.get(), StringSerializer.get(), BytesArraySerializer.get());
q.setColumnFamily(colFamName);
q.setKey(colFamKey);
// if no anonymous handler then use specific columns
if (cfMapDef.isColumnSliceRequired()) {
q.setColumnNames(cfMapDef.getSliceColumnNameArr());
} else {
q.setRange("", "", false, maxNumColumns);
}
QueryResult> result = q.execute();
if (null == result || null == result.get()) {
return null;
}
T obj = createObject(cfMapDef, pkObj, result.get());
return obj;
}
public T saveObj(Keyspace keyspace, T obj) {
if (null == obj) {
throw new IllegalArgumentException("object cannot be null");
}
@SuppressWarnings("unchecked")
CFMappingDef cfMapDef = (CFMappingDef) cacheMgr.getCfMapDef(obj.getClass(), true);
byte[] colFamKey = generateColumnFamilyKeyFromPojo(obj, cfMapDef);
String colFamName = cfMapDef.getEffectiveColFamName();
Mutator m = HFactory.createMutator(keyspace, BytesArraySerializer.get());
// if object contains collection, then must delete everything first - easier
// than reading the row and selectively deleting, which is an alternative if
// this is too destructive
if (cfMapDef.isAnyCollections()) {
m.addDeletion(colFamKey, colFamName);
}
// must create the "add" columns after the delete to insure proper processing order
Collection> colColl = createColumnSet(obj);
for (HColumn col : colColl) {
if (null == col.getName() || col.getName().isEmpty()) {
throw new HectorObjectMapperException(
"Column name cannot be null or empty - trying to persist to ColumnFamily, "
+ colFamName);
}
m.addInsertion(colFamKey, colFamName, col);
}
m.execute();
return obj;
}
// private void addDeletionsIfNecessary(Keyspace keyspace, CFMappingDef>
// cfMapDef, Object obj, Mutator m ) {
// // get collection properties and convert to columns of "info" records so we
// know what can/if be deleted
// Collection collColl =
// cfMapDef.getCollectionProperties();
//
//
// SliceQuery q = HFactory.createSliceQuery(keyspace,
// BytesArraySerializer.get(), StringSerializer.get(),
// BytesArraySerializer.get());
// q.setColumnFamily(cfMapDef.getColFamName());
// q.setColumnNames(columnNames);
// q.setKey(key);
//
// }
@SuppressWarnings("unchecked")
private byte[] generateColumnFamilyKeyFromPkObj(CFMappingDef> cfMapDef, Object pkObj) {
List segmentList = new ArrayList(cfMapDef.getKeyDef().getIdPropertyMap().size());
if (cfMapDef.getKeyDef().isComplexKey()) {
for (PropertyDescriptor pd : cfMapDef.getKeyDef().getPropertyDescriptorMap().values()) {
segmentList.add(callMethodAndConvertToCassandraType(pkObj, pd.getReadMethod(),
new DefaultConverter()));
}
} else {
PropertyMappingDefinition md = cfMapDef.getKeyDef().getIdPropertyMap().values().iterator()
.next();
segmentList.add(md.getConverter().convertObjTypeToCassType(pkObj));
}
return keyConcatStrategy.concat(segmentList);
}
private byte[] generateColumnFamilyKeyFromPojo(Object obj, CFMappingDef> cfMapDef) {
List segmentList = new ArrayList(cfMapDef.getKeyDef().getIdPropertyMap().size());
for (PropertyMappingDefinition md : cfMapDef.getKeyDef().getIdPropertyMap().values()) {
Method meth = md.getPropDesc().getReadMethod();
segmentList.add(callMethodAndConvertToCassandraType(obj, meth, md.getConverter()));
}
return keyConcatStrategy.concat(segmentList);
}
@SuppressWarnings("unchecked")
private byte[] callMethodAndConvertToCassandraType(Object obj, Method meth,
@SuppressWarnings("rawtypes") Converter converter) {
try {
Object retVal = meth.invoke(obj, (Object[]) null);
return converter.convertObjTypeToCassType(retVal);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
* Given a column slice from Hector/Cassandra and a type, this method
* instantiates the object and sets the properties on the object using the
* slice data. If a column doesn't map to an specific property in the object,
* it will see if the object has implemented the interface,
* {@link HectorExtraProperties}. If so call
* {@link HectorExtraProperties#addExtraProperty(String, String)}, on the
* object.
*
* @param id
* ID (row key) of the object we are retrieving from Cassandra
* @param clazz
* type of object to instantiate and populate
* @param slice
* column slice from Hector of type
* ColumnSlice
*
* @return instantiated object if success, null if slice is empty,
* RuntimeException otherwise
*/
T createObject(CFMappingDef cfMapDef, Object pkObj, ColumnSlice slice) {
if (slice.getColumns().isEmpty()) {
return null;
}
CFMappingDef extends T> cfMapDefInstance = determineClassType(cfMapDef, slice);
try {
T obj = cfMapDefInstance.getEffectiveClass().newInstance();
setIdIfCan(cfMapDef, obj, pkObj);
for (HColumn col : slice.getColumns()) {
String colName = col.getName();
PropertyMappingDefinition md = cfMapDefInstance.getPropMapByColumnName(colName);
if (null != md && null != md.getPropDesc()) {
if (!md.isCollectionType()) {
setPropertyUsingColumn(obj, col, md);
} else {
collMapperHelper.instantiateCollection(obj, col, md);
}
} else if (collMapperHelper.addColumnToCollection(cfMapDefInstance, obj, colName,
col.getValue())) {
continue;
}
// if this is a derived class then don't need to save discriminator
// column value
else if (null != cfMapDef.getDiscColumn() && colName.equals(cfMapDef.getDiscColumn())) {
continue;
} else {
addToExtraIfCan(obj, cfMapDef, col);
}
}
return obj;
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (SecurityException e) {
throw new RuntimeException(e);
}
}
/**
* Create Set of HColumns for the given Object. The Object must be annotated
* with {@link Column} on the desired fields.
*
* @param obj
* @return
*/
private Collection> createColumnSet(Object obj) {
Map> map = createColumnMap(obj);
if (null != map) {
return map.values();
} else {
return null;
}
}
/**
* Creates a Map of property names as key and HColumns as value. See #
*
* @param obj
* @return
*/
Map> createColumnMap(T obj) {
if (null == obj) {
throw new IllegalArgumentException("Class type cannot be null");
}
@SuppressWarnings("unchecked")
CFMappingDef cfMapDef = (CFMappingDef) cacheMgr.getCfMapDef((Class) obj.getClass(),
true);
try {
Map> colSet = new HashMap>();
Collection coll = cfMapDef.getAllProperties();
for (PropertyMappingDefinition md : coll) {
Collection> colColl = createColumnsFromProperty(obj, md);
if (null != colColl) {
for (HColumn col : colColl) {
colSet.put(col.getName(), col);
}
}
}
if (null != cfMapDef.getCfBaseMapDef()) {
CFMappingDef> cfSuperMapDef = cfMapDef.getCfBaseMapDef();
String discColName = cfSuperMapDef.getDiscColumn();
DiscriminatorType discType = cfSuperMapDef.getDiscType();
colSet.put(
discColName,
createHColumn(discColName, convertDiscTypeToColValue(discType, cfMapDef.getDiscValue())));
}
addAnonymousProperties(obj, colSet);
return colSet;
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
private void addAnonymousProperties(Object obj, Map> colSet)
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Method meth = cacheMgr.findAnnotatedMethod(obj.getClass(),
AnonymousPropertyCollectionGetter.class);
if (null == meth) {
return;
}
@SuppressWarnings("unchecked")
Collection> propColl = (Collection>) meth.invoke(
obj, (Object[]) null);
if (null == propColl || propColl.isEmpty()) {
return;
}
for (Entry entry : propColl) {
colSet.put(entry.getKey(), HFactory.createColumn(entry.getKey(), entry.getValue().getBytes(),
StringSerializer.get(), BytesArraySerializer.get()));
}
}
@SuppressWarnings({ "unchecked" })
private Collection> createColumnsFromProperty(T obj,
PropertyMappingDefinition md) throws SecurityException, NoSuchFieldException,
IllegalArgumentException, IllegalAccessException, InvocationTargetException {
if (!md.isCollectionType()) {
byte[] colValue = createBytesFromPropertyValue(obj, md);
if (null == colValue) {
return null;
}
return Arrays.asList(createHColumn(md.getColName(), colValue));
} else {
return createColumnsFromCollectionProperty(obj, md);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Collection> createColumnsFromCollectionProperty(T obj,
PropertyMappingDefinition md) throws SecurityException, NoSuchFieldException,
IllegalArgumentException, IllegalAccessException, InvocationTargetException {
// get collection
Object tmpColl = reflectionHelper.invokeGetter(obj, md);
if (!(tmpColl instanceof Collection)) {
throw new HectorObjectMapperException("property, " + md.getColName()
+ ", is marked as a collection type, but not actually a Collection. Property is type, "
+ md.getPropDesc().getPropertyType());
}
LinkedList> colList = new LinkedList>();
// save collection info
Collection coll = (Collection) tmpColl;
colList.add(createHColumn(md.getColName(), collMapperHelper.createCollectionInfoColValue(coll)));
// iterate over collection applying converter to its elements
int count = 0;
for (Object elem : coll) {
byte[] bytes = collMapperHelper.serializeCollectionValue(elem);
if (null == bytes) {
return null;
}
colList.add(createHColumn(
collMapperHelper.createCollectionItemColName(md.getColName(), count), bytes));
count++;
}
return colList;
}
private byte[] createBytesFromPropertyValue(Object obj, PropertyMappingDefinition md)
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Object retVal = reflectionHelper.invokeGetter(obj, md);
// if no value, then signal with null bytes
if (null == retVal) {
return null;
}
@SuppressWarnings("unchecked")
byte[] bytes = md.getConverter().convertObjTypeToCassType(retVal);
return bytes;
}
private HColumn createHColumn(String name, byte[] value) {
return HFactory.createColumn(name, value, StringSerializer.get(), BytesArraySerializer.get());
}
private CFMappingDef extends T> determineClassType(CFMappingDef cfMapDef,
ColumnSlice slice) {
if (null == cfMapDef.getInheritanceType()) {
return cfMapDef;
}
// if no columns we assume base class
if (null == slice || null == slice.getColumns() || slice.getColumns().isEmpty()) {
return cfMapDef;
}
// only support single table so use discriminator information
String discColName = cfMapDef.getDiscColumn();
DiscriminatorType discType = cfMapDef.getDiscType();
Map
© 2015 - 2025 Weber Informatics LLC | Privacy Policy