All Downloads are FREE. Search and download functionalities are using the official Maven repository.

me.prettyprint.hom.ClassCacheMgr Maven / Gradle / Ivy

package me.prettyprint.hom;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Inheritance;
import javax.persistence.Table;

import me.prettyprint.hom.annotations.AnonymousPropertyAddHandler;
import me.prettyprint.hom.cache.ColumnParser;
import me.prettyprint.hom.cache.HectorObjectMapperException;
import me.prettyprint.hom.cache.IdClassParserValidator;
import me.prettyprint.hom.cache.InheritanceParserValidator;
import me.prettyprint.hom.cache.TableParserValidator;
import me.prettyprint.hom.converters.DefaultConverter;

/**
 * Manage parsing and caching of class meta-data.
 * 
 * @author
 */
public class ClassCacheMgr {
  private Map> cfMapByColFamName = new HashMap>();
  private Map, CFMappingDef> cfMapByClazz = new HashMap, CFMappingDef>();

  private InheritanceParserValidator inheritanceParVal = new InheritanceParserValidator();
  private TableParserValidator tableParVal = new TableParserValidator();
  private IdClassParserValidator idClassParVal = new IdClassParserValidator();
  private ColumnParser columnPar = new ColumnParser();

  /**
   * Examine class hierarchy using {@link CFMappingDef} objects to discover the
   * given class' "base inheritance" class. A base inheritance class is
   * determined by {@link CFMappingDef#isBaseInheritanceClass()}
   * 
   * @param 
   * 
   * @param cfMapDef
   * @return returns the base in the ColumnFamily mapping hierarchy
   */
  public  CFMappingDef findBaseClassViaMappings(CFMappingDef cfMapDef) {
    CFMappingDef tmpDef = cfMapDef;
    CFMappingDef cfSuperDef;
    while (null != (cfSuperDef = tmpDef.getCfSuperMapDef())) {
      if (cfSuperDef.isBaseInheritanceClass()) {
        return cfSuperDef;
      }
      tmpDef = cfSuperDef;
    }
    return null;
  }

  /**
   * Retrieve class mapping meta-data by Class object.
   * 
   * @param 
   * @param clazz
   * @param throwException
   * @return CFMappingDef if found, exception if throwException = true, and null
   *         otherwise
   */
  public  CFMappingDef getCfMapDef(Class clazz, boolean throwException) {
    @SuppressWarnings("unchecked")
    CFMappingDef cfMapDef = (CFMappingDef) cfMapByClazz.get(clazz);
    if (null == cfMapDef && throwException) {
      throw new HectorObjectMapperException(
          "could not find property definitions for class, "
              + clazz.getSimpleName()
              + ", in class cache.  This indicates the EntityManager was not initialized properly.  If not using EntityManager the cache must be explicity initialized");

    }
    return cfMapDef;
  }

  /**
   * Retrieve class mapping meta-data by ColumnFamily name.
   * 
   * @param 
   * @param colFamName
   * @param throwException
   * @return CFMappingDef if found, exception if throwException = true, and null
   *         otherwise
   */
  public  CFMappingDef getCfMapDef(String colFamName, boolean throwException) {
    @SuppressWarnings("unchecked")
    CFMappingDef cfMapDef = (CFMappingDef) cfMapByColFamName.get(colFamName);
    if (null == cfMapDef && throwException) {
      throw new HectorObjectMapperException(
          "could not find property definitions for column family, "
              + colFamName
              + ", in class cache.  This indicates the EntityManager was not initialized properly.  If not using EntityManager the cache must be explicity initialized");

    }
    return cfMapDef;
  }

  /**
   * For each class that should be managed, this method must be called to parse
   * its annotations and derive its meta-data.
   * 
   * @param 
   * 
   * @param clazz
   * @return CFMapping describing the initialized class.
   */
  public  CFMappingDef initializeCacheForClass(Class clazz) {
    CFMappingDef cfMapDef = initializeColumnFamilyMapDef(clazz);
    try {
      initializePropertiesMapDef(cfMapDef);
    } catch (IntrospectionException e) {
      throw new HectorObjectMapperException(e);
    } catch (InstantiationException e) {
      throw new HectorObjectMapperException(e);
    } catch (IllegalAccessException e) {
      throw new HectorObjectMapperException(e);
    }

    // by the time we get here, all super classes and their annotations have
    // been processed and validated, and all annotations for this class have
    // been processed. what's left to do is validate this class, set super
    // classes, and and set any defaults
    checkMappingAndSetDefaults(cfMapDef);

    // if this class is not a derived class, then map the ColumnFamily name
    if (!cfMapDef.isDerivedClassInheritance()) {
      cfMapByColFamName.put(cfMapDef.getEffectiveColFamName(), cfMapDef);
    }

    // always map the parsed class to its ColumnFamily map definition
    cfMapByClazz.put(cfMapDef.getRealClass(), cfMapDef);

    return cfMapDef;
  }

  public Map getFieldPropertyDescriptorMap(Class clazz)
      throws IntrospectionException {
    Map pdMap = new HashMap();

    // get descriptors for all properties in POJO
    PropertyDescriptor[] pdArr = Introspector.getBeanInfo(clazz, clazz.getSuperclass())
                                             .getPropertyDescriptors();

    // if no property descriptors then return leaving empty annotation map
    if (null == pdArr || 0 == pdArr.length) {
      return pdMap;
    }

    // create tmp map for easy field -> descriptor mapping
    for (PropertyDescriptor pd : pdArr) {
      pdMap.put(pd.getName(), pd);
    }

    return pdMap;
  }

  private  void initializePropertiesMapDef(CFMappingDef cfMapDef)
      throws IntrospectionException, InstantiationException, IllegalAccessException {
    Class theType = cfMapDef.getEffectiveClass();

    Map pdMap = getFieldPropertyDescriptorMap(theType);
    if (pdMap.isEmpty() && !cfMapDef.isDerivedClassInheritance()) {
      throw new HectorObjectMapperException("could not find any properties annotated with @"
          + Column.class.getSimpleName());
    }

    Field[] fieldArr = theType.getDeclaredFields();

    // iterate over all declared fields (for this class only, no inherited
    // fields) processing annotations as we go
    for (Field f : fieldArr) {
      Annotation[] annoArr = f.getAnnotations();
      if (null == annoArr) {
        // TODO BTB:assume @Basic - fields are not required to be annotated to
        // be persisted if they are a "basic type" - see 2.8 and 11.1.6
        // @Transient to ignore a field
        continue;
      }

      for (Annotation anno : annoArr) {
        PropertyDescriptor pd = pdMap.get(f.getName());
        if (null == pd) {
          throw new HectorObjectMapperException("Property, "
              + cfMapDef.getEffectiveClass().getSimpleName() + "." + f.getName()
              + ", does not have proper setter/getter");
        }

        if (anno instanceof Column || anno instanceof me.prettyprint.hom.annotations.Column) {
          columnPar.parse(f, anno, pd, cfMapDef);
        } else if (anno instanceof Basic) {
//          basicPar.parse(f, anno, pd, cfMapDef);
        } else if (anno instanceof Id) {
          processIdAnnotation(f, (Id) anno, cfMapDef, pdMap);
        } else if (anno instanceof me.prettyprint.hom.annotations.Id) {
          processIdCustomAnnotation(f, (me.prettyprint.hom.annotations.Id) anno, cfMapDef, pdMap);
        }
      }
    }
  }

  private  void processIdAnnotation(Field f, Id anno, CFMappingDef cfMapDef,
      Map pdMap) throws InstantiationException, IllegalAccessException {
    // TODO lookup JPA 2 spec for class-level ids
    PropertyMappingDefinition md = new PropertyMappingDefinition(pdMap.get(f.getName()), null,
        DefaultConverter.class);
    if (null == md.getPropDesc() || null == md.getPropDesc().getReadMethod()
        || null == md.getPropDesc().getWriteMethod()) {
      throw new HectorObjectMapperException("@" + Id.class.getSimpleName()
          + " is defined on property, " + f.getName() + ", but its missing proper setter/getter");
    }
    cfMapDef.getKeyDef().addIdPropertyMap(md);
  }

  private  void processIdCustomAnnotation(Field f, me.prettyprint.hom.annotations.Id anno,
      CFMappingDef cfMapDef, Map pdMap)
      throws InstantiationException, IllegalAccessException {
    // TODO lookup JPA 2 spec for class-level ids
    PropertyMappingDefinition md = new PropertyMappingDefinition(pdMap.get(f.getName()), null,
        anno.converter());
    if (null == md.getPropDesc() || null == md.getPropDesc().getReadMethod()
        || null == md.getPropDesc().getWriteMethod()) {
      throw new HectorObjectMapperException("@" + Id.class.getSimpleName()
          + " is defined on property, " + f.getName() + ", but its missing proper setter/getter");
    }

    cfMapDef.getKeyDef().addIdPropertyMap(md);
  }

  private  CFMappingDef initializeColumnFamilyMapDef(Class realClass) {
    // if already init'd don't do it again - could have happened because of
    // inheritance - causes recursive processing for class hierarchy
    CFMappingDef cfMapDef = getCfMapDef(realClass, false);
    if (null != cfMapDef) {
      return cfMapDef;
    }

    cfMapDef = new CFMappingDef(realClass);

    Class effectiveType = cfMapDef.getEffectiveClass();
    CFMappingDef cfSuperMapDef = null;

    // if this class extends a super, then process it first
    if (null != effectiveType.getSuperclass()) {
      try {
        cfSuperMapDef = initializeCacheForClass(effectiveType.getSuperclass());
        cfMapDef.setCfSuperMapDef(cfSuperMapDef);
      } catch (HomMissingEntityAnnotationException e) {
        // ok, becuase may not have a super class that's an entity
      }
    }

    Annotation[] annoArr = effectiveType.getAnnotations();
    if (null == annoArr) {
      // TODO:btb see if this might be an error
      return cfMapDef;
    }

    for (Annotation anno : annoArr) {
      if (anno instanceof Table) {
        tableParVal.parse(this, anno, cfMapDef);
      } else if (anno instanceof IdClass) {
        idClassParVal.parse(this, anno, cfMapDef);
      } else if (anno instanceof Inheritance) {
        inheritanceParVal.parse(this, anno, cfMapDef);
      } else if (anno instanceof DiscriminatorColumn) {
        inheritanceParVal.parse(this, anno, cfMapDef);
      } else if (anno instanceof DiscriminatorValue) {
        inheritanceParVal.parse(this, anno, cfMapDef);
      }
    }

    return cfMapDef;
  }

  private  void checkMappingAndSetDefaults(CFMappingDef cfMapDef) {
    inheritanceParVal.validateAndSetDefaults(this, cfMapDef);
    tableParVal.validateAndSetDefaults(this, cfMapDef);
    idClassParVal.validateAndSetDefaults(this, cfMapDef);

    // must do this after tabeParVal validate
    checkForPojoPrimaryKey(cfMapDef);

    checkForAnonymousHandler(cfMapDef);

    generateColumnSliceIfNeeded(cfMapDef);

  }

  private void checkForPojoPrimaryKey(CFMappingDef cfMapDef) {
    // if we know it's a complex key then it must be present so we only check
    // case for simple one field key

    // SimpleTestBean breaks this check right now because it uses method
    // annotations which isn't supported by the ClassCacheMgr at this time
    // if (!cfMapDef.getKeyDef().isComplexKey()) {
    // if (!cfMapDef.getKeyDef().isSimpleIdPresent()) {
    // throw new HectorObjectMapperException("Entity, " +
    // cfMapDef.getRealClass().getName()
    // + ", is missing a primary key.  Must annotate at least one field with @"
    // + Id.class.getSimpleName() + " or use a complex primary key");
    // }
    // }
  }

  private  void checkForAnonymousHandler(CFMappingDef cfMapDef) {
    CFMappingDef tmpDef = cfMapDef;
    while (null != tmpDef) {
      Method meth = findAnnotatedMethod(cfMapDef.getEffectiveClass(),
          AnonymousPropertyAddHandler.class);
      if (null != meth) {
        cfMapDef.setAnonymousPropertyAddHandler(meth);
        return;
      }
      tmpDef = tmpDef.getCfSuperMapDef();
    }
  }

  private void generateColumnSliceIfNeeded(CFMappingDef cfMapDef) {
    if (!cfMapDef.isAnonymousHandlerAvailable()) {
      Collection coll = cfMapDef.getAllProperties();

      String[] daNames = new String[cfMapDef.isStandaloneClass() ? coll.size() : coll.size() + 1];
      Iterator iter = coll.iterator();
      int pos = 0;
      while (iter.hasNext()) {
        daNames[pos++] = iter.next().getColName();
      }

      // if an inheritance hierarchy exists we need to add in the discriminator
      // column
      if (!cfMapDef.isStandaloneClass()) {
        daNames[pos] = cfMapDef.getDiscColumn();
      }
      cfMapDef.setSliceColumnNameArr(daNames);
    }
  }

  /**
   * Find method annotated with the given annotation.
   * 
   * @param clazz
   * @param anno
   * @return returns Method if found, null otherwise
   */
  public Method findAnnotatedMethod(Class clazz, Class anno) {
    for (Method meth : clazz.getMethods()) {
      if (meth.isAnnotationPresent(anno)) {
        return meth;
      }
    }
    return null;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy