/*
* Copyright (c) 2012 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.db.common;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.impl.ColumnField;
import com.emc.storageos.db.client.impl.DataObjectType;
import com.emc.storageos.db.client.impl.GlobalLockType;
import com.emc.storageos.db.client.impl.SchemaRecordType;
import com.emc.storageos.db.client.impl.TimeSeriesType;
import com.emc.storageos.db.client.impl.TypeMap;
import com.emc.storageos.db.client.impl.DbClientImpl;
import com.emc.storageos.db.client.model.Cf;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.ExcludeFromGarbageCollection;
import com.emc.storageos.db.client.model.GeoVisibleResource;
import com.emc.storageos.db.client.model.GlobalLock;
import com.emc.storageos.db.client.model.SchemaRecord;
import com.emc.storageos.db.client.model.TimeSeries;
import com.emc.storageos.db.client.util.KeyspaceUtil;
import com.netflix.astyanax.model.ColumnFamily;
/**
* Scanner for sweeping all DataObject types defined and creates:
* - CF Map for building db schema
* - DependencyTracker - with all the dependency information between the types
*/
public class DataObjectScanner extends PackageScanner {
private static final Logger _log = LoggerFactory.getLogger(DataObjectScanner.class);
private Map<String, ColumnFamily> _cfMap;
private Map<String, ColumnFamily> _geocfMap;
private DependencyTracker _dependencyTracker;
private boolean _dualDbSvcMode;
private Properties _dbCommonInfo;
private Collection<Class<?>> modelClasses;
/**
* Get DependencyTracker
*
* @return
*/
public DependencyTracker getDependencyTracker() {
return _dependencyTracker;
}
/**
* Get ColumnFamily Map
*
* @return
*/
public Map<String, ColumnFamily> getCfMap() {
return _cfMap;
}
/**
* Get geo ColumnFamily Map
*
* @return
*/
public Map<String, ColumnFamily> getGeoCfMap() {
return _geocfMap;
}
// DBSVC config parameters
public void setDbCommonInfo(Properties dbCommonInfo) {
_dbCommonInfo = dbCommonInfo;
}
/**
* Scan model classes and load up CF information from them
*/
@SuppressWarnings("unchecked")
public void init() {
_dependencyTracker = new DependencyTracker();
_cfMap = new HashMap<String, ColumnFamily>();
_geocfMap = new HashMap<String, ColumnFamily>();
this.modelClasses = getModelClasses(Cf.class);
scan(Cf.class);
_dependencyTracker.buildDependencyLevels();
_log.info("DependencyTracker - state: {}", _dependencyTracker.toString());
}
/**
* Processes data object or time series class and extracts CF
* requirements
*
* @param clazz data object or time series class
*/
@SuppressWarnings("unchecked")
@Override
protected void processClass(Class clazz) {
if (DataObject.class.isAssignableFrom(clazz)) {
if (!isDualDbSvcMode()) {
addToTypeMap(clazz, _cfMap);
} else if (KeyspaceUtil.isLocal(clazz)) {
addToTypeMap(clazz, _cfMap);
} else if (KeyspaceUtil.isGlobal(clazz)) {
addToTypeMap(clazz, _geocfMap);
} else {
addToTypeMap(clazz, _geocfMap);
addToTypeMap(clazz, _cfMap);
}
} else if (TimeSeries.class.isAssignableFrom(clazz)) {
TimeSeriesType tsType = TypeMap.getTimeSeriesType(clazz);
ColumnFamily cf = tsType.getCf();
_cfMap.put(cf.getName(), cf);
if (tsType.getCompactOptimized() &&
_dbCommonInfo != null &&
Boolean.TRUE.toString().equalsIgnoreCase(
_dbCommonInfo.getProperty(DbClientImpl.DB_STAT_OPTIMIZE_DISK_SPACE, "false"))) {
// modify TTL for Compaction Enable Series types
int min_ttl = Integer.parseInt(_dbCommonInfo.getProperty(DbClientImpl.DB_LOG_MINIMAL_TTL,
"604800"));
if (min_ttl < tsType.getTtl()) {
_log.info("Setting TTL for the CF {} equal to {}", cf.getName(), min_ttl);
tsType.setTtl(min_ttl);
}
}
} else if (SchemaRecord.class.isAssignableFrom(clazz)) {
SchemaRecordType srType = TypeMap.getSchemaRecordType();
ColumnFamily cf = srType.getCf();
_cfMap.put(cf.getName(), cf);
} else if (GlobalLock.class.isAssignableFrom(clazz)) {
GlobalLockType glType = TypeMap.getGlobalLockType();
ColumnFamily cf = glType.getCf();
_geocfMap.put(cf.getName(), cf);
} else {
throw new IllegalStateException("Failed to process Class " + clazz.getName());
}
}
/**
* Check to see if dependency is valid for the dataobject, geo-visible resource
* or global lock. Make sure global and geo-visible objects are not referencing
* local objects.
*
* @param refType referenced dependency
* @param clazz the dataobject type
* @return true if validation passes
*/
private boolean isDependencyValidated(Class refType, Class clazz) {
_log.debug("Validating reference {} for Class {}", refType, clazz);
// If this is global dataobject, reference should be global or geo-visible
if (DataObject.class.isAssignableFrom(clazz) && KeyspaceUtil.isGlobal(clazz)) {
if (GeoVisibleResource.class.isAssignableFrom(refType)) {
return true;
} else if (DataObject.class.isAssignableFrom(refType) && KeyspaceUtil.isGlobal(refType)) {
return true;
} else {
return false;
}
}
return true;
}
private void addToTypeMap(Class clazz, Map<String, ColumnFamily> useCfMap) {
DependencyInterceptor dependencyInterceptor = new DependencyInterceptor(this.modelClasses);
Map<String, List<String>> indexCfTypeMap = new HashMap<String, List<String>>();
DataObjectType doType = TypeMap.getDoType(clazz);
useCfMap.put(doType.getCF().getName(), doType.getCF());
boolean include = (clazz.getAnnotation(ExcludeFromGarbageCollection.class) == null);
Iterator<ColumnField> it = doType.getColumnFields().iterator();
while (it.hasNext()) {
ColumnField field = it.next();
if (field.getIndex() == null) {
continue;
}
useCfMap.put(field.getIndexCF().getName(), field.getIndexCF());
// for dependency processing
if (field.getIndexRefType() != null) {
_log.info(" index: " + field.getIndex().getClass().getSimpleName() + " class: " + clazz.getSimpleName() + " field: "
+ field.getName() + " indexCF: " + field.getIndexCF().getName() + " reference: " + field.getIndexRefType());
if (isDuplicateIndexCf(field, indexCfTypeMap)) {
_log.error("Class: {} has muliple indexed columns of the same type configured to use the same index column family: {}",
clazz.getName(), field.getIndexCF().getName());
throw new IllegalStateException("Class: " + clazz.getName()
+ " has muliple indexed columns of the same type configured to use the same index column family: "
+ field.getIndexCF().getName());
}
// check first before adding the dependency
if (!isDependencyValidated(field.getIndexRefType(), clazz)) {
_log.error("Class: {} is a global object but it has illegal reference to a non-global dependency: {}", clazz.getName(),
field.getIndexRefType());
throw new IllegalStateException("Class: " + clazz.getName()
+ " is a global object but it has illegal reference to a non-global dependency: " + field.getIndexRefType());
}
if (dependencyInterceptor.isConcretModelClass(field.getIndexRefType())) {
_dependencyTracker.addDependency(field.getIndexRefType(), clazz, field);
} else {
_log.info("reference type is abstract class, need special process");
dependencyInterceptor.handleDependency(_dependencyTracker, clazz, field);
}
}
}
if (include) {
_dependencyTracker.includeClass(clazz);
} else {
_dependencyTracker.excludeClass(clazz);
}
}
/**
* @param field
* @param clazz
* @return
*/
private boolean isDuplicateIndexCf(ColumnField field, Map<String, List<String>> indexCfTypeMap) {
boolean isDuplicate = false;
if (indexCfTypeMap.get(field.getIndexCF().getName()) == null) {
List<String> typeList = new ArrayList<String>();
typeList.add(field.getIndexRefType().getSimpleName());
indexCfTypeMap.put(field.getIndexCF().getName(), typeList);
} else if (indexCfTypeMap.get(field.getIndexCF().getName()).contains(field.getIndexRefType().getSimpleName())) {
isDuplicate = true;
} else {
indexCfTypeMap.get(field.getIndexCF().getName()).add(field.getIndexRefType().getSimpleName());
}
return isDuplicate;
}
public boolean isDualDbSvcMode() {
return _dualDbSvcMode;
}
public void setDualDbSvcMode(boolean dualDbSvcMode) {
this._dualDbSvcMode = dualDbSvcMode;
}
}