package org.hivedb.hibernate;
import org.hivedb.Hive;
import org.hivedb.HiveLockableException;
import org.hivedb.HiveRuntimeException;
import org.hivedb.annotations.*;
import org.hivedb.configuration.*;
import org.hivedb.management.HiveConfigurationSchemaInstaller;
import org.hivedb.meta.PartitionDimension;
import org.hivedb.meta.SecondaryIndex;
import org.hivedb.meta.persistence.CachingDataSourceProvider;
import org.hivedb.util.Lists;
import org.hivedb.util.PrimitiveUtils;
import org.hivedb.util.classgen.ReflectionTools;
import org.hivedb.util.database.JdbcTypeMapper;
import org.hivedb.util.functional.Atom;
import org.hivedb.util.functional.Filter;
import org.hivedb.util.functional.Predicate;
import org.springframework.beans.BeanUtils;
import java.lang.reflect.Method;
import java.util.*;
public class ConfigurationReader {
private Map<String, EntityConfig> hiveConfigs = new HashMap<String, EntityConfig>();
private PartitionDimension dimension = null;
public ConfigurationReader(PartitionDimension partitionDimension) {
dimension = partitionDimension;
}
public ConfigurationReader(Class<?>... classes) {
for (Class<?> clazz : classes)
if (isHiveEntity(clazz))
configure(clazz);
}
public ConfigurationReader(Collection<Class<?>> classes) {
for (Class<?> clazz : classes)
if (isHiveEntity(clazz))
configure(clazz);
}
private boolean isHiveEntity(Class<?> clazz) {
return clazz.isAnnotationPresent(Resource.class);
}
public EntityConfig configure(Class<?> clazz) {
EntityConfig config = readConfiguration(clazz);
if (dimension == null)
dimension = extractPartitionDimension(clazz);
else if (!dimension.getName().equals(config.getPartitionDimensionName()))
throw new UnsupportedOperationException(
String.format("You are trying to configure on object from partition dimension %s into a Hive configured to use partition dimension %s. This is not supported. Use a separate configuration for each dimension.", config.getPartitionDimensionName(), dimension.getName()));
hiveConfigs.put(clazz.getName(), config);
return config;
}
@SuppressWarnings("unchecked")
public static PartitionDimension extractPartitionDimension(Class clazz) {
Method partitionIndexMethod = AnnotationHelper.getFirstMethodWithAnnotation(clazz, PartitionIndex.class);
return new PartitionDimension(getPartitionDimensionName(clazz), JdbcTypeMapper.primitiveTypeToJdbcType(partitionIndexMethod.getReturnType()));
}
public static EntityConfig readConfiguration(Class<?> clazz) {
PartitionDimension dimension = extractPartitionDimension(clazz);
Method versionMethod = AnnotationHelper.getFirstMethodWithAnnotation(clazz, EntityVersion.class);
Method resourceIdMethod = AnnotationHelper.getFirstMethodWithAnnotation(clazz, EntityId.class);
Method partitionIndexMethod = AnnotationHelper.getFirstMethodWithAnnotation(clazz, PartitionIndex.class);
String primaryIndexPropertyName = getIndexNameForMethod(partitionIndexMethod);
String idPropertyName = getIndexNameForMethod(resourceIdMethod);
String versionPropertyName = versionMethod == null ? null : getIndexNameForMethod(versionMethod);
List<EntityIndexConfig> indexes = createIndexMethods(clazz, resourceIdMethod);
EntityConfig config = new EntityConfigImpl(
clazz,
dimension.getName(),
getResourceName(clazz),
primaryIndexPropertyName,
idPropertyName,
versionPropertyName,
indexes,
partitionIndexMethod.getName().equals(resourceIdMethod.getName())
);
return config;
}
private static List<EntityIndexConfig> createIndexMethods(Class<?> clazz, Method resourceIdMethod) {
Collection<Method> indexMethods = getIndexMethods(clazz, resourceIdMethod);
List<EntityIndexConfig> indexes = Lists.newArrayList();
for (Method indexMethod : indexMethods)
if (isCollectionPropertyOfAComplexType(clazz, indexMethod))
if (isIndexDelegate(indexMethod))
indexes.add(new EntityIndexConfigProxy(clazz, getSecondaryIndexName(indexMethod), getIndexPropertyOfCollectionType(ReflectionTools.getCollectionItemType(clazz, ReflectionTools.getPropertyNameOfAccessor(indexMethod))), readConfiguration(getHiveForeignKeyIndexClass(indexMethod))));
else
indexes.add(new EntityIndexConfigImpl(clazz, getSecondaryIndexName(indexMethod), getIndexPropertyOfCollectionType(ReflectionTools.getCollectionItemType(clazz, ReflectionTools.getPropertyNameOfAccessor(indexMethod)))));
else if (isIndexDelegate(indexMethod))
indexes.add(new EntityIndexConfigProxy(clazz, getSecondaryIndexName(indexMethod), readConfiguration(getHiveForeignKeyIndexClass(indexMethod))));
else
indexes.add(new EntityIndexConfigImpl(clazz, getSecondaryIndexName(indexMethod)));
return indexes;
}
@SuppressWarnings("unchecked")
private static Collection<Method> getIndexMethods(Class<?> clazz, final Method resourceIdMethod) {
return Filter.grep(new Predicate<Method>() {
public boolean f(Method method) {
return !method.equals(resourceIdMethod);
}
}, AnnotationHelper.getAllMethodsWithAnnotations(clazz, (Collection) Arrays.asList(Index.class, PartitionIndex.class)));
}
private static boolean isIndexDelegate(Method indexMethod) {
return indexMethod.getAnnotation(IndexDelegate.class) != null;
}
private static Class<?> getHiveForeignKeyIndexClass(Method indexMethod) {
try {
return Class.forName(indexMethod.getAnnotation(IndexDelegate.class).value());
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class not found " + indexMethod.getAnnotation(IndexDelegate.class).value());
}
}
private static boolean isCollectionPropertyOfAComplexType(Class<?> clazz, Method indexMethod) {
return ReflectionTools.isCollectionProperty(clazz, ReflectionTools.getPropertyNameOfAccessor(indexMethod)) &&
!PrimitiveUtils.isPrimitiveClass(ReflectionTools.getCollectionItemType(clazz, ReflectionTools.getPropertyNameOfAccessor(indexMethod)));
}
@SuppressWarnings("unchecked")
private static String getIndexPropertyOfCollectionType(Class collectionType) {
try {
return ReflectionTools.getPropertyNameOfAccessor(AnnotationHelper.getFirstMethodWithAnnotation(collectionType, Index.class));
}
catch (Exception e) {
throw new RuntimeException(String.format("Unable to find an Index annotation for collection type %s", collectionType.getName()));
}
}
public Collection<EntityConfig> getConfigurations() {
return hiveConfigs.values();
}
public EntityConfig getEntityConfig(String className) {
return hiveConfigs.get(className);
}
public EntityHiveConfig getHiveConfiguration() {
EntityConfig prototype = Atom.getFirstOrThrow(hiveConfigs.values());
return new PluralHiveConfig(hiveConfigs, prototype.getPartitionDimensionName(), prototype.getPrimaryKeyClass());
}
public void install(String uri) {
new HiveConfigurationSchemaInstaller(uri).run();
install(Hive.load(uri, CachingDataSourceProvider.getInstance()));
}
public void install(Hive hive) {
Hive target = hive;
if (hive.getPartitionDimension() == null) {
target = Hive.create(hive.getUri(), dimension.getName(), dimension.getColumnType(), CachingDataSourceProvider.getInstance(), null);
}
for (EntityConfig config : hiveConfigs.values())
installConfiguration(config, target);
}
public void installConfiguration(EntityConfig config, Hive hive) {
try {
// Duplicate installations are possible due to delegated indexes
if (hive.doesResourceExist(config.getResourceName()))
return;
org.hivedb.meta.Resource resource =
hive.addResource(createResource(config));
for (EntityIndexConfig indexConfig : (Collection<EntityIndexConfig>) config.getEntityIndexConfigs())
if (indexConfig.getIndexType().equals(IndexType.Delegates))
installConfiguration(((EntityIndexConfigDelegator) indexConfig).getDelegateEntityConfig(), hive);
else if (indexConfig.getIndexType().equals(IndexType.Hive)) {
hive.addSecondaryIndex(resource, createSecondaryIndex(indexConfig));
}
} catch (HiveLockableException e) {
throw new HiveRuntimeException(e.getMessage(), e);
}
}
private SecondaryIndex createSecondaryIndex(EntityIndexConfig config) {
return new SecondaryIndex(config.getIndexName(), JdbcTypeMapper.primitiveTypeToJdbcType(config.getIndexClass()));
}
private org.hivedb.meta.Resource createResource(EntityConfig config) {
return new org.hivedb.meta.Resource(
config.getResourceName(),
JdbcTypeMapper.primitiveTypeToJdbcType(config.getIdClass()),
config.isPartitioningResource());
}
public static String getResourceName(Class<?> clazz) {
Resource resource = clazz.getAnnotation(Resource.class);
if (resource != null)
return resource.value();
else
return getResourceNameForClass(clazz);
}
private static String getPartitionDimensionName(Class<?> clazz) {
String name = AnnotationHelper.getFirstInstanceOfAnnotation(clazz, PartitionIndex.class).value();
Method m = AnnotationHelper.getFirstMethodWithAnnotation(clazz, PartitionIndex.class);
return "".equals(name) ? getIndexNameForMethod(m) : name;
}
private static String getSecondaryIndexName(Method method) {
Index annotation = method.getAnnotation(Index.class);
if (annotation != null && !"".equals(annotation.name()))
return annotation.name();
else
return getIndexNameForMethod(method);
}
public static String getIndexNameForMethod(Method method) {
return BeanUtils.findPropertyForMethod(method).getDisplayName();
}
public static String getResourceNameForClass(Class<?> clazz) {
return clazz.getName().replace('.', '_');
}
}