package com.appmetr.hercules.metadata; import com.appmetr.hercules.HerculesConfig; import com.appmetr.hercules.annotations.*; import com.appmetr.hercules.driver.DataDriver; import com.appmetr.hercules.keys.CollectionKeysExtractor; import com.appmetr.hercules.keys.EntityCollectionKeyExtractor; import com.appmetr.hercules.keys.ForeignKey; import com.appmetr.hercules.keys.SerializableKeyCollectionKeyExtractor; import com.appmetr.hercules.manager.EntityManager; import com.appmetr.hercules.serializers.AbstractHerculesSerializer; import com.appmetr.hercules.serializers.SerializerProvider; import com.google.common.base.Strings; import com.google.inject.Inject; import com.google.inject.Injector; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * Clients of this class should call only extract() method. * Other methods visible(public) only for testing purposes. */ public class EntityMetadataExtractor { @Inject HerculesConfig herculesConfig; @Inject EntityManager em; @Inject private DataDriver dataDriver; @Inject private SerializerProvider serializerProvider; @Inject Injector injector; /** * Use this method to extract metadata from entity class. * * @return extracted metadata. */ public EntityMetadata extract(Class<?> clazz) { EntityMetadata metadata = new EntityMetadata(); EntityAnnotationsValidator.isClassEntity(clazz); EntityAnnotationsValidator.validateThatOnlyOneIdPresent(clazz); EntityAnnotationsValidator.validateIndexes(clazz); Set<String> indexColumnFamilies = new HashSet<String>(); parseClassLevelMetadata(clazz, metadata, indexColumnFamilies); parseFieldLevelMetadata(clazz, metadata, indexColumnFamilies); return metadata; } private void parseClassLevelMetadata(Class<?> clazz, EntityMetadata metadata, Set<String> indexColumnFamilies) { Entity entityAnnotation = clazz.getAnnotation(Entity.class); metadata.setColumnFamily(entityAnnotation.columnFamily().length() == 0 ? clazz.getSimpleName() : entityAnnotation.columnFamily()); metadata.setEntityClass(clazz); MetadataExtractorUtils.setEntityComparatorType(clazz, metadata, entityAnnotation.comparatorType()); MetadataExtractorUtils.setEntitySerializer(clazz, metadata); MetadataExtractorUtils.setEntityTTL(clazz, metadata); metadata.setListenerMetadata(MetadataExtractorUtils.getListenerMetadata(clazz)); //Parse primary key metadata metadata.setCreatePrimaryKeyIndex(clazz.isAnnotationPresent(PKIndex.class)); if (clazz.isAnnotationPresent(Id.class)) { Id primaryKeyAnnotation = clazz.getAnnotation(Id.class); KeyMetadata primaryKeyMetadata = new KeyMetadata(); if (primaryKeyAnnotation.keyClass().equals(Object.class)) { throw new RuntimeException("Class level " + Id.class.getSimpleName() + " annotation for class " + clazz.getSimpleName() + " should contain keyClass field"); } Class primaryKeyClass = primaryKeyAnnotation.keyClass(); primaryKeyMetadata.setKeyClass(primaryKeyClass); if (!primaryKeyAnnotation.serializer().equals(AbstractHerculesSerializer.class)) { primaryKeyMetadata.setSerializer(primaryKeyAnnotation.serializer()); } else if (primaryKeyClass.isAnnotationPresent(Serializer.class)) { primaryKeyMetadata.setSerializer(((Serializer) primaryKeyClass.getAnnotation(Serializer.class)).value()); } metadata.setPrimaryKeyMetadata(primaryKeyMetadata); } //Parse indexes metadata if (clazz.isAnnotationPresent(Index.class)) { Index indexAnnotation = clazz.getAnnotation(Index.class); indexColumnFamilies.add(parseFKMetadata(indexAnnotation, metadata)); } if (clazz.isAnnotationPresent(Indexes.class)) { Indexes indexesAnnotation = clazz.getAnnotation(Indexes.class); Index[] indexAnnotations = indexesAnnotation.value(); for (Index index : indexAnnotations) { String addedIndexName = parseFKMetadata(index, metadata); if (indexColumnFamilies.contains(addedIndexName)) { throw new RuntimeException("Index configuration problem for class " + clazz.getName() + ". Several indexes with the same name present. Name = " + addedIndexName); } indexColumnFamilies.add(addedIndexName); } } } private void parseFieldLevelMetadata(Class<?> clazz, EntityMetadata metadata, Set<String> indexColumnFamilies) { for (Field field : clazz.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers())) continue; field.setAccessible(true); //primary key if (field.isAnnotationPresent(Id.class)) { if (field.isAnnotationPresent(Transient.class)) { throw new RuntimeException("field annotated with @Id cannot be @Transient. field: [" + field.getName() + "] in class: " + clazz.getName()); } if (field.isAnnotationPresent(GeneratedGUID.class)) { metadata.setPrimaryKeyGenerated(true); } KeyMetadata primaryKeyMetadata = new KeyMetadata(); Class primaryKeyClass = field.getType(); primaryKeyMetadata.setField(field); primaryKeyMetadata.setKeyClass(primaryKeyClass); Id idAnnotation = field.getAnnotation(Id.class); if (!idAnnotation.serializer().equals(AbstractHerculesSerializer.class)) { primaryKeyMetadata.setSerializer(idAnnotation.serializer()); } else if (primaryKeyClass.isAnnotationPresent(Serializer.class)) { primaryKeyMetadata.setSerializer(((Serializer) primaryKeyClass.getAnnotation(Serializer.class)).value()); } metadata.setPrimaryKeyMetadata(primaryKeyMetadata); } else { if (!field.isAnnotationPresent(Transient.class)) { // create column only for non Transient fields String columnName = getColumnName(field); metadata.setColumnClass(columnName, field.getType()); metadata.setFieldColumn(field, columnName); if (field.isAnnotationPresent(Serializer.class)) { metadata.setColumnSerializer(columnName, field.getAnnotation(Serializer.class).value()); } if (field.isAnnotationPresent(NotNullField.class)) { metadata.addNotNullColumn(columnName); } } if (field.isAnnotationPresent(IndexedCollection.class)) { String addedIndexCF = parseCollectionIndexMetadata(field, field.getAnnotation(IndexedCollection.class), metadata); if (indexColumnFamilies.contains(addedIndexCF)) { throw new RuntimeException("Incorrect @IndexedCollection usage for field [" + field.getName() + "] of class " + metadata.getEntityClass().getName() + " Several indexes with the same name present. Name=" + addedIndexCF); } indexColumnFamilies.add(addedIndexCF); } } } if (metadata.getFieldToColumn().size() == 0) { throw new RuntimeException("Entity " + clazz.getSimpleName() + " should contain at least one field different from Id"); } } private String parseFKMetadata(Index indexAnnotation, EntityMetadata metadata) { Class<? extends ForeignKey> keyClass = indexAnnotation.keyClass(); ForeignKeyMetadata keyMetadata = new ForeignKeyMetadata(); String cfName = metadata.getColumnFamily() + "_" + keyClass.getSimpleName(); keyMetadata.setColumnFamily(cfName); keyMetadata.setKeyClass(keyClass); Field[] declaredFields = keyClass.getDeclaredFields(); for (Field field : declaredFields) { if (!field.isAnnotationPresent(Transient.class)) { field.setAccessible(true); keyMetadata.addKeyField(field); } } if (!indexAnnotation.serializer().equals(AbstractHerculesSerializer.class)) { keyMetadata.setSerializer(indexAnnotation.serializer()); } else if (keyClass.isAnnotationPresent(Serializer.class)) { keyMetadata.setSerializer((keyClass.getAnnotation(Serializer.class)).value()); } else { throw new RuntimeException("Could not find serializer for class \"" + keyClass.getSimpleName() + "\""); } metadata.addIndex(keyClass, keyMetadata); return cfName; } private String parseCollectionIndexMetadata(Field field, IndexedCollection indexAnnotation, EntityMetadata metadata) { CollectionIndexMetadata indexMetadata = new CollectionIndexMetadata(); Class<?> itemClass = null; if (!Object.class.equals(indexAnnotation.itemClass())) { itemClass = indexAnnotation.itemClass(); } Class<? extends AbstractHerculesSerializer> serializerClass = null; if (!AbstractHerculesSerializer.class.equals(indexAnnotation.serializer())) { serializerClass = indexAnnotation.serializer(); } Class<? extends CollectionKeysExtractor> keyExtractorClass = null; if (!CollectionKeysExtractor.class.equals(indexAnnotation.keyExtractorClass())) { keyExtractorClass = indexAnnotation.keyExtractorClass(); } if (keyExtractorClass != null) { if (serializerClass != null || itemClass != null) { throw new RuntimeException("Incorrect @IndexedCollection usage for field [" + field.getName() + "] of class " + metadata.getEntityClass().getName() + ". If keyExtractorClass is specified, serializer and itemClass shouldn't be set"); } } else { if (itemClass == null) { throw new RuntimeException("Incorrect @IndexedCollection usage for field [" + field.getName() + "] of class " + metadata.getEntityClass().getName() + ". itemClass or keyExtractorClass should be specified"); } } CollectionKeysExtractor keysExtractor; if (keyExtractorClass != null) { keysExtractor = injector.getInstance(keyExtractorClass); } else { if (!Collection.class.isAssignableFrom(field.getType())) { throw new RuntimeException("Incorrect @IndexedCollection usage for field [" + field.getName() + "] of class " + metadata.getEntityClass().getName() + ". Indexed field should be a collection"); } if (serializerClass != null) { //collection of objects with special serializer keysExtractor = new SerializableKeyCollectionKeyExtractor(field, serializerProvider.getSerializer(serializerClass, itemClass)); } else { if (herculesConfig.getEntityClasses().contains(itemClass)) { //collection of hercules entities keysExtractor = new EntityCollectionKeyExtractor(field, itemClass, em); } else if (itemClass.getAnnotation(Serializer.class) != null) { //collection of primary keys with serializers Serializer s = itemClass.getAnnotation(Serializer.class); keysExtractor = new SerializableKeyCollectionKeyExtractor(field, serializerProvider.getSerializer(s.value(), itemClass)); } else { //collection of well known objects with hector serializers keysExtractor = new SerializableKeyCollectionKeyExtractor(field, dataDriver.getSerializerForClass(itemClass)); } } } indexMetadata.setKeyExtractor(keysExtractor); indexMetadata.setIndexedField(field); String name = field.getName(); if (!Strings.isNullOrEmpty(indexAnnotation.name())) { name = indexAnnotation.name(); } name = metadata.getColumnFamily() + "_" + name; indexMetadata.setIndexColumnFamily(name); metadata.getCollectionIndexes().put(field.getName(), indexMetadata); return name; } private String getColumnName(Field field) { Column column = field.getAnnotation(Column.class); if (column != null) { return column.name(); } else { return field.getName(); } } }