/*
* Copyright (c) 2008-2012 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.db.client.impl;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.astyanax.ColumnListMutation;
import com.netflix.astyanax.model.ByteBufferRange;
import com.netflix.astyanax.model.Column;
import com.netflix.astyanax.model.ColumnFamily;
import com.netflix.astyanax.serializers.StringSerializer;
import com.emc.storageos.db.client.model.AbstractChangeTrackingMap;
import com.emc.storageos.db.client.model.AbstractChangeTrackingSet;
import com.emc.storageos.db.client.model.AbstractChangeTrackingSetMap;
import com.emc.storageos.db.client.model.AbstractSerializableNestedObject;
import com.emc.storageos.db.client.model.AlternateId;
import com.emc.storageos.db.client.model.ClockIndependent;
import com.emc.storageos.db.client.model.ClockIndependentValue;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.DecommissionedIndex;
import com.emc.storageos.db.client.model.Encrypt;
import com.emc.storageos.db.client.model.EncryptionProvider;
import com.emc.storageos.db.client.model.Id;
import com.emc.storageos.db.client.model.IndexByKey;
import com.emc.storageos.db.client.model.Name;
import com.emc.storageos.db.client.model.NamedRelationIndex;
import com.emc.storageos.db.client.model.NamedURI;
import com.emc.storageos.db.client.model.PermissionsIndex;
import com.emc.storageos.db.client.model.PrefixIndex;
import com.emc.storageos.db.client.model.Relation;
import com.emc.storageos.db.client.model.RelationIndex;
import com.emc.storageos.db.client.model.ScopedLabel;
import com.emc.storageos.db.client.model.ScopedLabelIndex;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.Ttl;
import com.emc.storageos.db.client.model.AggregatedIndex;
import com.emc.storageos.db.client.model.ClassNameTimeSeries;
import com.emc.storageos.db.client.model.NoInactiveIndex;
import com.emc.storageos.db.client.model.TimeSeriesAlternateId;
import com.emc.storageos.db.exceptions.DatabaseException;
/**
* Column / data object field type metadata
*/
public class ColumnField <T extends CompositeIndexColumnName> {
private static final Logger _log = LoggerFactory.getLogger(ColumnField.class);
// types of columns object mapper supports
public static enum ColumnType {
Id,
Primitive,
NamedURI,
TrackingSet,
TrackingMap,
TrackingSetMap,
NestedObject
}
// types of indexing object mapper supports
public static enum IndexKind {
Prefix,
ScopedLabel,
Relation,
NamedRelation,
AltId,
Permissions,
Decommissioned,
}
private final DataObjectType _parentType;
private final PropertyDescriptor _property;
private String _name;
private ColumnType _colType;
private final Class _valueType;
private Class<? extends DataObject> _mappedByType;
private String _mappedByField;
private Integer _ttl;
private DbIndex _index;
private boolean _indexByKey;
private Class<? extends DataObject> _indexRefType;
// encrypted field?
private boolean _encrypt;
// time independent?
private Class<? extends ClockIndependentValue> clockIndValue;
// deactivate object when last referenced object is removed
private boolean deactivateIfEmpty;
// not set for MultiValue[List|Map] types
private CompositeColumnName compositeName;
private boolean lazyLoaded;
public boolean isLazyLoaded() {
return lazyLoaded;
}
private List<ColumnField> refFields = new ArrayList<>();
private List<ColumnField> dependentFields = new ArrayList<>();
// Reference and Dependent fields indicate cross-reference and/or dependence of fields of an object.
// They are used when a field can not be serialized on its own and need reference fields to complete index.
// Refenced Field is a filed of this Dataobject that is accessed by the current field during serialization
public List<ColumnField> getRefFields() {
return refFields;
}
// Dependent Fields depend on (i.e. might need) the current field for serialization
public List<ColumnField> getDependentFields() {
return dependentFields;
}
/**
* Constructor
*
* @param pd
*/
public ColumnField(DataObjectType doType, PropertyDescriptor pd) {
_parentType = doType;
_property = pd;
_valueType = _property.getPropertyType();
processProperty();
}
/**
* Get property descriptor for this field
*
* @return
*/
public PropertyDescriptor getPropertyDescriptor() {
return _property;
}
/**
* Column type
*
* @return
*/
public ColumnType getType() {
return _colType;
}
/**
* Column name or prefix (for set and map)
*
* @return
*/
public String getName() {
return _name;
}
/**
* Get index CF
*
* @return
*/
public ColumnFamily<String, T> getIndexCF() {
return _index.getIndexCF();
}
/**
* Get reference type of the indexed field
*
* @return
*/
public Class<? extends DataObject> getIndexRefType() {
return _indexRefType;
}
/**
* Get parent type as string
*
* @return
*/
public Class<? extends DataObject> getDataObjectType() {
return _parentType.getDataObjectClass();
}
/**
* Get whether the column is encrypted
*
* @return true if encrypted, false otherwise.
*/
public boolean isEncrypted() {
return _encrypt;
}
/**
* Sets/overrides TTL value
*/
public void setTtl(Integer ttl) {
this._ttl = ttl;
}
/**
* Get current TTL configuration
*
* @return
*/
public Integer getTtl() {
return _ttl;
}
/**
* Get value of deactivateIfEmpty
*
* @return
*/
public boolean deactivateIfEmpty() {
return deactivateIfEmpty;
}
/**
* Returns prefix index row key
*
* @param text
* @return
*/
public String getPrefixIndexRowKey(String text) {
if (_index instanceof PrefixDbIndex) {
return ((PrefixDbIndex) _index).getRowKey(text);
}
if (_index instanceof ScopedLabelDbIndex) {
return ((ScopedLabelDbIndex) _index).getRowKey(text);
}
throw new RuntimeException(String.format("The index %s is not a PrefixDbIndex or ScopedLabelDbIndex", _index));
}
/**
* Returns scoped prefix index row key
*
* @param label
* @return
*/
public String getPrefixIndexRowKey(ScopedLabel label) {
if (label.getScope() != null) {
return String.format("%s:%s", label.getScope(), getPrefixIndexRowKey(label.getLabel()));
} else {
return getPrefixIndexRowKey(label.getLabel());
}
}
/**
* Build column slice range for given prefix
*
* @param prefix
* @return
*/
public ByteBufferRange buildPrefixRange(String prefix, int pageSize) {
String target = prefix.toLowerCase();
return CompositeColumnNameSerializer.get().buildRange()
.withPrefix(_parentType.getDataObjectClass().getSimpleName())
.greaterThanEquals(target)
.lessThanEquals(target + Character.MAX_VALUE)
.limit(pageSize)
.build();
}
/**
* Build column slice range for given string
*
* @param prefix
* @return
*/
public ByteBufferRange buildMatchRange(String prefix, int pageSize) {
String target = prefix.toLowerCase();
return CompositeColumnNameSerializer.get().buildRange()
.withPrefix(_parentType.getDataObjectClass().getSimpleName())
.greaterThanEquals(target)
.lessThanEquals(target)
.limit(pageSize)
.build();
}
/**
* Deserializes column into object field
*
* @param column column to deserialize
* @param obj object containing this field
* @throws DatabaseException
*/
public void deserialize(Column<CompositeColumnName> column, Object obj) {
if (_encrypt && _parentType.getEncryptionProvider() != null) {
deserializeEncryptedColumn(column, obj, _parentType.getEncryptionProvider());
} else {
ColumnValue.setField(column, _property, obj);
}
}
/**
* Deserializes an encrypted column into object field
*
* @param column column to deserialize
* @param obj object containing this field
* @param encryptionProvider the encryption provider used to decrypt the column
* @throws DatabaseException
*/
public void deserializeEncryptedColumn(Column<CompositeColumnName> column, Object obj,
EncryptionProvider encryptionProvider) {
if (!_encrypt) {
throw new IllegalArgumentException("column is not encrypted");
}
if (encryptionProvider == null) {
throw new IllegalArgumentException("null encryption provider");
}
ColumnValue.setEncryptedStringField(column, _property, obj, encryptionProvider);
}
/**
* Generate queries for removing given column. Caller is expected to execute generated queries
*
* @param recordKey record row key
* @param column column to
* @param mutator row mutator that holds remove queries
* @param fieldColumnMap column map for the record. it might need to remove indexes for dependent fields
*/
public boolean removeColumn(String recordKey, Column<CompositeColumnName> column, RowMutator mutator,
Map<String, List<Column<CompositeColumnName>>> fieldColumnMap) {
CompositeColumnName columnName = column.getName();
// remove record
mutator.getRecordColumnList(_parentType.getCF(), recordKey).deleteColumn(columnName);
if (_index == null || column instanceof ColumnWrapper || isDeletionMark(column)) {
return false;
}
// remove index
return _index.removeColumn(recordKey, column, _parentType.getDataObjectClass().getSimpleName(), mutator, fieldColumnMap);
}
private void addDeletionMark(String recordKey, CompositeColumnName colName, RowMutator mutator) {
addColumn(recordKey, colName, null, mutator);
}
public static boolean isDeletionMark(Column<CompositeColumnName> column) {
return !column.getByteBufferValue().hasRemaining();
}
private boolean removeColumn(String recordKey, Column<CompositeColumnName> column, RowMutator mutator) {
return removeColumn(recordKey, column, mutator, null);
}
public boolean removeIndex(String recordKey, Column<CompositeColumnName> column, RowMutator mutator,
Map<String, List<Column<CompositeColumnName>>> fieldColumnMap,
DataObject obj) {
// remove index
return _index.removeColumn(recordKey, column, _parentType.getDataObjectClass().getSimpleName(), mutator, fieldColumnMap, obj);
}
public boolean removeIndex(String recordKey, Column<CompositeColumnName> column, RowMutator mutator,
Map<String, List<Column<CompositeColumnName>>> fieldColumnMap) {
// remove index
return _index.removeColumn(recordKey, column, _parentType.getDataObjectClass().getSimpleName(), mutator, fieldColumnMap);
}
/**
* Generate queries for inserting a given column. Caller is expected to
* execute generated queries
*
* @param recordKey record key
* @param column column name
* @param val column value
* @param mutator mutator that holds insertion queries
* @param obj for which the column is added
* @throws DatabaseException
*/
private boolean addColumn(String recordKey, CompositeColumnName column, Object val,
RowMutator mutator, DataObject obj) {
if (_encrypt && _parentType.getEncryptionProvider() != null) {
val = _parentType.getEncryptionProvider().encrypt((String) val);
}
// insert record
ColumnListMutation<CompositeColumnName> recordColList =
mutator.getRecordColumnList(_parentType.getCF(), recordKey);
ColumnValue.setColumn(recordColList, column, val, _ttl);
if (_index == null || val == null) {
return false;
}
// insert index
return _index.addColumn(recordKey, column, val, _parentType.getDataObjectClass().getSimpleName(),
mutator, _ttl, obj);
}
private boolean addColumn(String recordKey, CompositeColumnName column, Object val,
RowMutator mutator) {
return addColumn(recordKey, column, val, mutator, null);
}
/**
* Serializes object field into database updates
*
* @param obj data object to serialize
* @param mutator row mutator to hold insertion queries
* @return boolean
* @throws DatabaseException
*/
public boolean serialize(DataObject obj, RowMutator mutator) {
try {
String id = obj.getId().toString();
if (isLazyLoaded() || _property.getReadMethod() == null) {
return false;
}
Object val = _property.getReadMethod().invoke(obj);
if (val == null) {
return false;
}
boolean changed = false;
switch (_colType) {
case NamedURI:
case Primitive: {
if (!obj.isChanged(_name)) {
return false;
}
changed = addColumn(id, getColumnName(null, mutator), val, mutator, obj);
break;
}
case TrackingSet: {
AbstractChangeTrackingSet valueSet = (AbstractChangeTrackingSet) val;
Set<?> addedSet = valueSet.getAddedSet();
if (addedSet != null) {
Iterator<?> it = valueSet.getAddedSet().iterator();
while (it.hasNext()) {
Object itVal = it.next();
String targetVal = valueSet.valToString(itVal);
changed |= addColumn(id, getColumnName(targetVal, mutator), itVal, mutator);
}
}
Set<?> removedVal = valueSet.getRemovedSet();
if (removedVal != null) {
Iterator<?> removedIt = removedVal.iterator();
while (removedIt.hasNext()) {
String targetVal = valueSet.valToString(removedIt.next());
if (_index == null) {
changed |= removeColumn(id, new ColumnWrapper(getColumnName(targetVal, mutator), targetVal), mutator);
} else {
addDeletionMark(id, getColumnName(targetVal, mutator), mutator);
changed = true;
}
}
}
break;
}
case TrackingMap: {
AbstractChangeTrackingMap valueMap = (AbstractChangeTrackingMap) val;
Set<String> changedSet = valueMap.getChangedKeySet();
if (changedSet != null) {
Iterator<String> it = valueMap.getChangedKeySet().iterator();
while (it.hasNext()) {
String key = it.next();
Object entryVal = valueMap.get(key);
CompositeColumnName colName = getColumnName(key, mutator);
if (clockIndValue != null) {
int ordinal = ((ClockIndependentValue) entryVal).ordinal();
colName = getColumnName(key, String.format("%08d", ordinal), mutator);
}
changed |= addColumn(id, colName, valueMap.valToByte(entryVal),
mutator);
}
}
Set<String> removedKey = valueMap.getRemovedKeySet();
if (removedKey != null) {
Iterator<String> removedIt = removedKey.iterator();
while (removedIt.hasNext()) {
String key = removedIt.next();
CompositeColumnName colName = getColumnName(key, mutator);
if (clockIndValue != null) {
Object removedVal = valueMap.getRemovedValue(key);
if (removedVal != null) {
colName = getColumnName(key, String.format("%08d",
((ClockIndependentValue) removedVal).ordinal()), mutator);
}
}
if (_index == null) {
changed |= removeColumn(id, new ColumnWrapper(colName, null), mutator);
} else {
addDeletionMark(id, colName, mutator);
changed = true;
}
}
}
break;
}
case TrackingSetMap: {
AbstractChangeTrackingSetMap valueMap = (AbstractChangeTrackingSetMap) val;
Set<String> keys = valueMap.keySet();
if (keys != null) {
Iterator<String> it = keys.iterator();
while (it.hasNext()) {
String key = it.next();
AbstractChangeTrackingSet valueSet = valueMap.get(key);
Set<?> addedSet = valueSet.getAddedSet();
if (addedSet != null) {
Iterator<?> itSet = valueSet.getAddedSet().iterator();
while (itSet.hasNext()) {
String value = valueSet.valToString(itSet.next());
changed |= addColumn(id, getColumnName(key, value, mutator), value, mutator);
}
}
Set<?> removedVal = valueSet.getRemovedSet();
if (removedVal != null) {
Iterator<?> removedIt = removedVal.iterator();
while (removedIt.hasNext()) {
String targetVal = valueSet.valToString(removedIt.next());
if (_index == null) {
changed |= removeColumn(id,
new ColumnWrapper(getColumnName(key, targetVal, mutator), targetVal), mutator);
} else {
addDeletionMark(id, getColumnName(key, targetVal, mutator), mutator);
changed = true;
}
}
}
}
}
break;
}
case NestedObject: {
if (!obj.isChanged(_name)) {
break;
}
AbstractSerializableNestedObject nestedObject = (AbstractSerializableNestedObject) val;
changed |= addColumn(id, getColumnName(null, mutator), nestedObject.toBytes(), mutator);
}
}
return changed;
} catch (final InvocationTargetException e) {
throw DatabaseException.fatals.serializationFailedId(obj.getId(), e);
} catch (final IllegalAccessException e) {
throw DatabaseException.fatals.serializationFailedId(obj.getId(), e);
}
}
/**
* Get column name for this field
*
* @param two second component of column name
* @param three third component of column name
* @param mutator row mutator with timestamp
* @return
*/
private CompositeColumnName getColumnName(String two, String three, RowMutator mutator) {
switch (_colType) {
case Id: {
return compositeName;
}
case NamedURI:
case Primitive:
case NestedObject: {
if (isInactiveField() || needIndexConsistency()) {
return new CompositeColumnName(_name, null, mutator.getTimeUUID());
} else {
return compositeName;
}
}
case TrackingMap:
case TrackingSet: {
if (_index == null) {
return new CompositeColumnName(_name, two, three);
} else {
return new CompositeColumnName(_name, two, three, mutator.getTimeUUID());
}
}
case TrackingSetMap: {
if (_index == null) {
return new CompositeColumnName(_name, two, three);
} else {
return new CompositeColumnName(_name, two, three, mutator.getTimeUUID());
}
}
}
return null;
}
/**
* Time UUID is always required for inactive field. We depends on the
* timestamp to validate modification time of inactive field
*
* @return
*/
private boolean isInactiveField() {
return _property.getName().equals(DataObject.INACTIVE_FIELD_NAME);
}
/**
* Time UUID is required to ensure index CF and object CF consistency: both
* are updated in single shot - all or nothing
*
* @return
*/
private boolean needIndexConsistency() {
return _index != null && _index.needConsistency();
}
/**
* Overload without third component
*
* @param two
* @param mutator
* @return
*/
private CompositeColumnName getColumnName(String two, RowMutator mutator) {
return getColumnName(two, null, mutator);
}
/**
* Sets the field as changed, for a complete overwrite
* used from migration handlers to create index entries
*
* @param obj
* @return
* @throws DatabaseException
*/
public void setChanged(DataObject obj) {
try {
Object val = _property.getReadMethod().invoke(obj);
if (val == null) {
return;
}
switch (_colType) {
case TrackingSet: {
AbstractChangeTrackingSet valueSet = (AbstractChangeTrackingSet) val;
valueSet.markAllForOverwrite();
break;
}
case TrackingMap: {
AbstractChangeTrackingMap valueMap = (AbstractChangeTrackingMap) val;
valueMap.markAllForOverwrite();
break;
}
case TrackingSetMap: {
AbstractChangeTrackingSetMap valueMap = (AbstractChangeTrackingSetMap) val;
Set<String> keys = valueMap.keySet();
if (keys != null) {
Iterator<String> it = keys.iterator();
while (it.hasNext()) {
String key = it.next();
AbstractChangeTrackingSet valueSet = valueMap.get(key);
valueSet.markAllForOverwrite();
}
}
break;
}
}
} catch (final InvocationTargetException e) {
throw DatabaseException.fatals.serializationFailedId(obj.getId(), e);
} catch (final IllegalAccessException e) {
throw DatabaseException.fatals.serializationFailedId(obj.getId(), e);
}
}
/**
* Helper to reflect on PropertyDescriptor and fill out _type and _name
* information;
*/
private void processProperty() {
// ignore if no get method
if (_property.getReadMethod() == null) {
return;
}
Method readMethod = _property.getReadMethod();
Annotation[] annotations = readMethod.getAnnotations();
ColumnFamily<String, IndexColumnName> indexCF = null;
int minPrefixChars;
boolean isLazyLoadable = false;
boolean hasRelationIndex = false;
for (int i = 0; i < annotations.length; i++) {
Annotation a = annotations[i];
if (a instanceof Id) {
_colType = ColumnType.Id;
_name = "Id";
} else if (a instanceof Name) {
_name = ((Name) a).value();
if (Number.class.isAssignableFrom(_valueType) ||
_valueType == URI.class ||
_valueType == String.class ||
_valueType == Date.class ||
_valueType == Boolean.class ||
_valueType == Byte.class ||
_valueType == Long.class ||
_valueType == byte[].class ||
_valueType.isEnum() ||
_valueType == Calendar.class) {
_colType = ColumnType.Primitive;
compositeName = new CompositeColumnName(_name);
} else if (NamedURI.class == _valueType) {
_colType = ColumnType.NamedURI;
compositeName = new CompositeColumnName(_name);
} else if (AbstractChangeTrackingSet.class.isAssignableFrom(_valueType)) {
_colType = ColumnType.TrackingSet;
} else if (AbstractChangeTrackingMap.class.isAssignableFrom(_valueType)) {
_colType = ColumnType.TrackingMap;
} else if (AbstractChangeTrackingSetMap.class.isAssignableFrom(_valueType)) {
_colType = ColumnType.TrackingSetMap;
} else if (AbstractSerializableNestedObject.class.isAssignableFrom(_valueType)) {
_colType = ColumnType.NestedObject;
compositeName = new CompositeColumnName(_name);
} else if (Collection.class.isAssignableFrom(_valueType)
|| DataObject.class.isAssignableFrom(_valueType)) {
isLazyLoadable = true;
} else {
throw new IllegalArgumentException(_name + " " + _valueType + " " + _property
+ " " + _parentType.getDataObjectClass());
}
} else if (a instanceof Ttl) {
_ttl = ((Ttl) a).value();
} else if (a instanceof RelationIndex) {
indexCF = new ColumnFamily<String, IndexColumnName>(
((RelationIndex) a).cf(), StringSerializer.get(), IndexColumnNameSerializer.get());
_indexRefType = ((RelationIndex) a).type();
deactivateIfEmpty = ((RelationIndex) a).deactivateIfEmpty();
_index = new RelationDbIndex(indexCF);
} else if (a instanceof AlternateId) {
indexCF = new ColumnFamily<String, IndexColumnName>(((AlternateId) a).value(), StringSerializer.get(),
IndexColumnNameSerializer.get());
_index = new AltIdDbIndex(indexCF);
} else if (a instanceof ClassNameTimeSeries) {
ColumnFamily<String, ClassNameTimeSeriesIndexColumnName> newIndexCF =
new ColumnFamily<String, ClassNameTimeSeriesIndexColumnName>(((ClassNameTimeSeries) a).value(), StringSerializer.get(),
ClassNameTimeSeriesSerializer.get());
_index = new ClassNameTimeSeriesDBIndex(newIndexCF);
} else if (a instanceof TimeSeriesAlternateId) {
ColumnFamily<String, TimeSeriesIndexColumnName> newIndexCF=
new ColumnFamily<String, TimeSeriesIndexColumnName>(((TimeSeriesAlternateId) a).value(), StringSerializer.get(),
TimeSeriesColumnNameSerializer.get());
_index = new TimeSeriesDbIndex(newIndexCF);
} else if (a instanceof NamedRelationIndex) {
indexCF = new ColumnFamily<String, IndexColumnName>(((NamedRelationIndex) a).cf(), StringSerializer.get(),
IndexColumnNameSerializer.get());
_indexRefType = ((NamedRelationIndex) a).type();
_index = new NamedRelationDbIndex(indexCF);
} else if (a instanceof PrefixIndex) {
indexCF = new ColumnFamily<String, IndexColumnName>(((PrefixIndex) a).cf(), StringSerializer.get(),
IndexColumnNameSerializer.get());
minPrefixChars = ((PrefixIndex) a).minChars();
_index = new PrefixDbIndex(indexCF, minPrefixChars);
} else if (a instanceof PermissionsIndex && AbstractChangeTrackingSetMap.class.isAssignableFrom(_valueType)) {
indexCF = new ColumnFamily<String, IndexColumnName>(((PermissionsIndex) a).value(), StringSerializer.get(),
IndexColumnNameSerializer.get());
_index = new PermissionsDbIndex(indexCF);
} else if (a instanceof Encrypt && _valueType == String.class) {
_encrypt = true;
} else if (a instanceof ScopedLabelIndex) {
ScopedLabelIndex scopeLabelIndex = (ScopedLabelIndex) a;
indexCF = new ColumnFamily<String, IndexColumnName>(scopeLabelIndex.cf(),
StringSerializer.get(), IndexColumnNameSerializer.get());
minPrefixChars = scopeLabelIndex.minChars();
_index = new ScopedLabelDbIndex(indexCF, minPrefixChars);
} else if (a instanceof ClockIndependent) {
clockIndValue = ((ClockIndependent) a).value();
} else if (a instanceof DecommissionedIndex && Boolean.class.isAssignableFrom(_valueType)) {
if (!_property.getName().equals(DataObject.INACTIVE_FIELD_NAME)
|| _parentType.getDataObjectClass().getAnnotation(NoInactiveIndex.class) == null) {
indexCF = new ColumnFamily<String, IndexColumnName>(((DecommissionedIndex) a).value(), StringSerializer.get(),
IndexColumnNameSerializer.get());
_index = new DecommissionedDbIndex(indexCF);
}
} else if (a instanceof IndexByKey &&
(AbstractChangeTrackingMap.class.isAssignableFrom(_valueType) ||
AbstractChangeTrackingSet.class.isAssignableFrom(_valueType))) {
_indexByKey = true;
} else if (a instanceof Relation) {
hasRelationIndex = true;
if (((Relation) a).type().equals(DataObject.class)) {
_mappedByType = _valueType;
} else {
_mappedByType = ((Relation) a).type();
}
_mappedByField = ((Relation) a).mappedBy();
} else if (a instanceof AggregatedIndex) {
indexCF = new ColumnFamily<String, IndexColumnName>(
((AggregatedIndex) a).cf(), StringSerializer.get(), IndexColumnNameSerializer.get());
String groupBy = ((AggregatedIndex) a).groupBy();
boolean global = ((AggregatedIndex) a).classGlobal();
_index = new AggregateDbIndex(indexCF, groupBy, global);
}
}
if (_name == null) {
String className = _parentType.getDataObjectClass().getName();
String fieldName = _property.getName();
throw new IllegalArgumentException(
String.format("@Name annotation missing from field '%s' in class '%s'", fieldName, className));
}
if (_index != null) {
_index.setFieldName(_name);
_index.setIndexByKey(_indexByKey);
}
if (isLazyLoadable && hasRelationIndex) {
lazyLoaded = true;
}
}
public final DbIndex getIndex() {
return _index;
}
private Object getFieldValue(DataObject obj) {
try {
return _property.getReadMethod().invoke(obj);
} catch (final InvocationTargetException e) {
throw DatabaseException.fatals.serializationFailedId(obj.getId(), e);
} catch (final IllegalAccessException e) {
throw DatabaseException.fatals.serializationFailedId(obj.getId(), e);
}
}
public static Object getFieldValue(ColumnField field, DataObject obj) {
return field.getFieldValue(obj);
}
void check() {
Method method = _property.getReadMethod();
Annotation[] annotations = method.getAnnotations();
for (Annotation a : annotations) {
boolean foundError = false;
if (a instanceof IndexByKey) {
String errMsgHeader = String.format("The method %s.%s() has @IndexByKey but:",
_parentType.getDataObjectClass().getName(), method.getName());
String errMsg = errMsgHeader;
if (_index == null) {
errMsg += "\nwithout an index annotation";
_log.error(errMsg);
foundError = true;
}
if (!AbstractChangeTrackingMap.class.isAssignableFrom(_valueType) &&
!AbstractChangeTrackingSet.class.isAssignableFrom(_valueType)) {
String warnMsg = errMsgHeader + "\nThe return type should be subclass of AbstractChangeTrackingMap/Set";
_log.warn(warnMsg);
}
if (foundError) {
throw DatabaseException.fatals.invalidAnnotation("@IndexByKey", errMsg);
}
}
}
}
/**
* @return the _mapedByType
*/
public Class<? extends DataObject> getMappedByType() {
return _mappedByType;
}
/**
* @return the _mappedByField
*/
public String getMappedByField() {
return _mappedByField;
}
public void prepareForLazyLoading(DataObject obj, LazyLoader lazyLoader, StringSet mappedBy) {
if (isLazyLoaded()) {
try {
if (java.util.List.class.isAssignableFrom(_valueType)) {
LazyLoadedList list = new LazyLoadedList(_name, obj, lazyLoader, mappedBy);
_property.getWriteMethod().invoke(obj, list);
} else if (java.util.Set.class.isAssignableFrom(_valueType)) {
LazyLoadedSet list = new LazyLoadedSet(_name, obj, lazyLoader, mappedBy);
_property.getWriteMethod().invoke(obj, list);
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
_log.error(e.getMessage(), e);
}
}
}
/**
* Get whether this field has IndexByKey annotation
* @return true if has, false otherwise.
*/
public boolean isIndexByKey() {
return _indexByKey;
}
}