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

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

There is a newer version: 3.0-04
Show newest version
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.util.HashMap;
import java.util.Map;

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.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.Converter;
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();

  /**
   * 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 
   * @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 = (CFMappingDef) cfSuperDef;
    }
    return null;
  }

  /**
   * Retrieve class mapping meta-data by Class object.
   * 
   * @param 
   * @param 
   * @param clazz
   * @param throwException
   * @return
   */
  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 
   * @param colFamName
   * @param throwException
   * @return
   */
  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  
   * 
   * @param clazz
   * @return
   */
  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
        continue;
      }

      for (Annotation anno : annoArr) {
        if (anno instanceof Column) {
          processColumnAnnotation(f, (Column) anno, cfMapDef, pdMap);
        } else if (anno instanceof me.prettyprint.hom.annotations.Column) {
          processColumnCustomAnnotation(f, (me.prettyprint.hom.annotations.Column) anno, cfMapDef,
              pdMap);
        } 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 processColumnAnnotation(Field f, Column anno, CFMappingDef cfMapDef,
      Map pdMap) throws InstantiationException, IllegalAccessException {
    PropertyDescriptor pd = pdMap.get(f.getName());
    if (null == pd) {
      throw new HectorObjectMapperException("Property, "
          + cfMapDef.getEffectiveClass().getSimpleName() + "." + f.getName()
          + ", does not have proper setter/getter");
    }

    PropertyMappingDefinition md = new PropertyMappingDefinition(pd, anno.name(),
        DefaultConverter.class);
    cfMapDef.addPropertyDefinition(md);
  }

  private  void processColumnCustomAnnotation(Field f,
      me.prettyprint.hom.annotations.Column anno, CFMappingDef cfMapDef,
      Map pdMap) throws InstantiationException, IllegalAccessException {
    PropertyDescriptor pd = pdMap.get(f.getName());
    if (null == pd) {
      throw new HectorObjectMapperException("Property, "
          + cfMapDef.getEffectiveClass().getSimpleName() + "." + f.getName()
          + ", does not have proper setter/getter");
    }

    PropertyMappingDefinition md = new PropertyMappingDefinition(pd, anno.name(),
        (Class>) anno.converter());
    cfMapDef.addPropertyDefinition(md);
    cfMapDef.addPropertyDefinition(md);
  }

  private  void processIdAnnotation(Field f, Id anno, CFMappingDef cfMapDef,
      Map pdMap) throws InstantiationException, IllegalAccessException {
    // TODO lookup JPA 2 spec for class-level ids
    @SuppressWarnings("unchecked")
    PropertyMappingDefinition md = new PropertyMappingDefinition(pdMap.get(f.getName()), null,
        DefaultConverter.class);
    if (null != md) {
      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.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, (Class>) anno.converter());
    if (null != md) {
      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.addIdPropertyMap(md);

    // @SuppressWarnings("unchecked")
    // PropertyMappingDefinition md =
    // new PropertyMappingDefinition(pdMap.get(f.getName()), null,
    // (Class>) idAnno
    // .converter());
    // if (null != md) {
    // if (null == md.getPropDesc() || null == md.getPropDesc().getReadMethod()
    // || null == md.getPropDesc().getWriteMethod()) {
    // throw new IllegalStateException("@" + Id.class.getSimpleName() +
    // " is defined on property, "
    // + f.getName() + ", but its missing proper setter/getter");
    // }
    // }
    // cfMapDef.setIdPropertyMap(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;
    }

    // try {
    cfMapDef = new CFMappingDef(realClass);
    // }
    // catch ( HectorObjectMapperException e ) {
    // // ok, becuase may not have a super class that's an entity
    // return null;
    // }

    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 (HectorObjectMapperException 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);
  }
}