Package com.alvazan.orm.impl.meta.scan

Source Code of com.alvazan.orm.impl.meta.scan.ScannerForField

package com.alvazan.orm.impl.meta.scan;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Provider;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alvazan.orm.api.base.CursorToMany;
import com.alvazan.orm.api.base.ToOneProvider;
import com.alvazan.orm.api.base.anno.NoSqlColumn;
import com.alvazan.orm.api.base.anno.NoSqlConverter;
import com.alvazan.orm.api.base.anno.NoSqlDiscriminatorColumn;
import com.alvazan.orm.api.base.anno.NoSqlEmbeddable;
import com.alvazan.orm.api.base.anno.NoSqlEmbedded;
import com.alvazan.orm.api.base.anno.NoSqlEntity;
import com.alvazan.orm.api.base.anno.NoSqlId;
import com.alvazan.orm.api.base.anno.NoSqlIndexed;
import com.alvazan.orm.api.base.anno.NoSqlInheritance;
import com.alvazan.orm.api.base.anno.NoSqlManyToMany;
import com.alvazan.orm.api.base.anno.NoSqlManyToOne;
import com.alvazan.orm.api.base.anno.NoSqlOneToMany;
import com.alvazan.orm.api.base.anno.NoSqlOneToOne;
import com.alvazan.orm.api.base.anno.NoSqlPartitionByThisField;
import com.alvazan.orm.api.base.spi.KeyGenerator;
import com.alvazan.orm.api.z5api.NoConversion;
import com.alvazan.orm.api.z8spi.conv.Converter;
import com.alvazan.orm.api.z8spi.conv.StandardConverters;
import com.alvazan.orm.api.z8spi.meta.DboTableMeta;
import com.alvazan.orm.api.z8spi.meta.ReflectionUtil;
import com.alvazan.orm.impl.meta.data.IdInfo;
import com.alvazan.orm.impl.meta.data.MetaAbstractClass;
import com.alvazan.orm.impl.meta.data.MetaClassInheritance;
import com.alvazan.orm.impl.meta.data.MetaClassSingle;
import com.alvazan.orm.impl.meta.data.MetaCommonField;
import com.alvazan.orm.impl.meta.data.MetaCursorField;
import com.alvazan.orm.impl.meta.data.MetaEmbeddedEntity;
import com.alvazan.orm.impl.meta.data.MetaEmbeddedSimple;
import com.alvazan.orm.impl.meta.data.MetaField;
import com.alvazan.orm.impl.meta.data.MetaIdField;
import com.alvazan.orm.impl.meta.data.MetaInfo;
import com.alvazan.orm.impl.meta.data.MetaProxyField;
import com.alvazan.orm.impl.meta.data.MetaToManyField;

@SuppressWarnings("rawtypes")
public class ScannerForField {
  private static final Logger log = LoggerFactory.getLogger(ScannerForField.class);
 
  @Inject
  private MetaInfo metaInfo;
  @Inject
  private Provider<MetaIdField> idMetaProvider;
  @Inject
  private Provider<MetaCommonField> metaProvider;
  @Inject
  private Provider<MetaToManyField> metaListProvider;
  @Inject
  private Provider<MetaEmbeddedEntity> metaEmbeddedProvider;
  @Inject
  private Provider<MetaEmbeddedSimple> metaEmbeddedSimpleProvider;
  @Inject
  private Provider<MetaCursorField> metaCursorProvider;
  @Inject
  private Provider<MetaProxyField> metaProxyProvider;
 
  private Map<Class, Converter> customConverters = new HashMap<Class, Converter>();

 
  public ScannerForField() {

  }
 
  @SuppressWarnings("unchecked")
  public <T> MetaIdField<T> processId(DboTableMeta t, Field field, MetaAbstractClass<T> metaClass) {

    Method idMethod = getIdMethod(field);
   
    NoSqlId idAnno = field.getAnnotation(NoSqlId.class);
    MetaIdField<T> metaField = idMetaProvider.get();
    KeyGenerator gen = null;
    if(idAnno.usegenerator()) {
      Class<? extends KeyGenerator> generation = idAnno.generation();
      gen = ReflectionUtil.create(generation);
    }
   
    Class<?> type = field.getType();
    Converter converter = null;
    if(!NoConversion.class.isAssignableFrom(idAnno.customConverter()))
      converter = ReflectionUtil.create(idAnno.customConverter());
   
    String columnName = field.getName();
    if(!"".equals(idAnno.columnName()))
      columnName = idAnno.columnName();
   
    boolean isIndexed = false;
    if(field.isAnnotationPresent(NoSqlIndexed.class))
      isIndexed = true;
   
    if(field.isAnnotationPresent(NoSqlPartitionByThisField.class))
      throw new IllegalArgumentException("Field="+field+" is a primary key so it cannot have annotation="+NoSqlPartitionByThisField.class.getName());
   
    converter = lookupConverter(field, type, converter);
    if(converter == null)
      throw throwInvalidConverter(field);
    IdInfo info = new IdInfo();
    info.setIdMethod(idMethod);
    info.setConverter(converter);
    info.setGen(gen);
    info.setUseGenerator(idAnno.usegenerator());
    info.setMetaClass(metaClass);
    metaField.setup(t, info, field, columnName, isIndexed);
    return metaField;
  }

  private IllegalArgumentException throwInvalidConverter(Field field) {
    Class type = field.getType();
    if(Date.class.equals(type) || Calendar.class.equals(type))
      return new IllegalArgumentException("See this url for what you did wrong: https://github.com/deanhiller/playorm/wiki/Date-and-Calendar-Support");
   
    //TODO: create url with information on this one..
    return new IllegalArgumentException("No converter found for field='"+field.getName()+"' in class="
        +field.getDeclaringClass()+".  You need to either add one of the @*ToOne annotations, @Embedded, @Transient " +
            "or add your own converter calling EntityMgrFactory.setup(Map<Class, Converter>) which " +
            "will then work for all fields of that type OR add @NoSqlConverter(converter=YourConverter.class)" +
            " or finally if we missed a standard converter, we need to add it in file InspectorField.java" +
            " in the constructor and it is trivial code(and we can copy the existing pattern)");
  }

  private Method getIdMethod(Field field) {
    String name = field.getName();
    String newName = name.substring(0,1).toUpperCase() + name.substring(1);
    String methodName = "get"+newName;
   
    Class<?> declaringClass = field.getDeclaringClass();
    try {
      Method method = declaringClass.getDeclaredMethod(methodName);
      if(!method.getReturnType().equals(field.getType()))
        throw new IllegalArgumentException("The method="+declaringClass.getName()+"."+methodName+" must" +
            " return the type="+field.getType().getName()+" but instead returns="+method.getReturnType().getName());
     
      return method;
    } catch (SecurityException e) {
      throw new RuntimeException("security issue on looking up method="+declaringClass.getName()+"."+methodName, e);
    } catch (NoSuchMethodException e) {
      throw new IllegalArgumentException("You are missing a method "+declaringClass.getName()+"."+methodName
          +"  This method exists as when you call it on a proxy, we make sure we do NOT hit the database" +
          " and instead just return the id that is inside the proxy.  Without this, we can't tell the" +
          " difference between a call to getName(where we have to hit the db and fill the proxy in) and" +
          " a call to just getting the id", e);
    }
  }

  public MetaField processColumn(DboTableMeta t, Field field) {
    NoSqlColumn col = field.getAnnotation(NoSqlColumn.class);
    MetaCommonField metaField = metaProvider.get();
    String colName = field.getName();
    if(col != null) {
      if(!"".equals(col.columnName()))
        colName = col.columnName();
    }

    boolean isIndexed = false;
    boolean byKeyOnly = false;
    if(field.isAnnotationPresent(NoSqlIndexed.class)) {
      isIndexed = true;
      NoSqlIndexed indexAnno = field.getAnnotation(NoSqlIndexed.class);
      byKeyOnly = indexAnno.byKeyOnly();
    }     
   
    boolean isPartitioned = false;
    if(field.isAnnotationPresent(NoSqlPartitionByThisField.class))
      isPartitioned = true;
     
    Class<?> type = field.getType();
    Converter converter = null;
    if(col != null && !NoConversion.class.isAssignableFrom(col.customConverter()))
      converter = ReflectionUtil.create(col.customConverter());

    converter = lookupConverter(field, type, converter);
    if(converter == null)
      throw throwInvalidConverter(field);
    metaField.setup(t, field, colName, converter, isIndexed, isPartitioned, byKeyOnly);
    return metaField;     
  }
 
  private Converter lookupConverter(Field field, Class<?> type, Converter custom) {
    NoSqlConverter customConv = field.getAnnotation(NoSqlConverter.class);
    if(customConv != null) {
      Class<? extends Converter> convClazz = customConv.converter();
      return ReflectionUtil.create(convClazz);
    } else if(custom != null) {
      return custom;
    } else if(customConverters.get(type) != null) {
      return customConverters.get(type);
    } else if(StandardConverters.get(type) != null){
      return StandardConverters.get(type);
    }
    return null;
  }

  public void setCustomConverters(Map<Class, Converter> converters) {
    if(converters == null)
      return; //nothing to do
   
    this.customConverters = converters;
  }

  public MetaField processManyToOne(DboTableMeta t, Field field) {
    NoSqlManyToOne annotation = field.getAnnotation(NoSqlManyToOne.class);
    String colName = annotation.columnName();
    return processToOne(t, field, colName);
  }

  public MetaField processOneToOne(DboTableMeta t, Field field) {
    NoSqlOneToOne annotation = field.getAnnotation(NoSqlOneToOne.class);
    String colName = annotation.columnName();
   
    return processToOne(t, field, colName);
  }
 
  public MetaField processManyToMany(MetaClassSingle<?> metaClass, DboTableMeta t, Field field) {
    NoSqlManyToMany annotation = field.getAnnotation(NoSqlManyToMany.class);
    String colName = annotation.columnName();
    String keyFieldForMap = annotation.keyFieldForMap();
   
    return processToManyRelationship(metaClass, t, field, colName, keyFieldForMap);   
  }
 
  public MetaField processOneToMany(MetaClassSingle<?> ownerMeta, DboTableMeta t, Field field) {
    NoSqlOneToMany annotation = field.getAnnotation(NoSqlOneToMany.class);
    String colName = annotation.columnName();
    String keyFieldForMap = annotation.keyFieldForMap();
   
    return processToManyRelationship(ownerMeta, t, field, colName, keyFieldForMap);
  }

  private MetaField processToManyRelationship(MetaClassSingle<?> metaClass, DboTableMeta t, Field field, String colNameOrig,
      String keyFieldForMap) {
    String colName = field.getName();
    if(!"".equals(colNameOrig))
      colName = colNameOrig;
   
    if(field.isAnnotationPresent(NoSqlPartitionByThisField.class))
      throw new IllegalArgumentException("Field="+field+" is ToMany annotation so it cannot have annotation="+NoSqlPartitionByThisField.class.getName());
   
    Field fieldForKey = null;

    ParameterizedType genType = (ParameterizedType) field.getGenericType();
    Class entityType;
    if(field.getType().equals(Map.class)) {
      if("".equals(keyFieldForMap))
        throw new RuntimeException("Field="+field+" is a Map so @OneToMany annotation REQUIRES a keyFieldForMap attribute which is the field name in the child entity to use as the key");
     
      entityType = (Class) genType.getActualTypeArguments()[1];
     
      String fieldName = keyFieldForMap;
     
      try {
        fieldForKey = entityType.getDeclaredField(fieldName);
        fieldForKey.setAccessible(true);
      } catch (NoSuchFieldException e) {
        throw new RuntimeException("The annotation OneToMany on field="+field+" references a field="+fieldName+" that does not exist on entity="+entityType.getName());
      } catch (SecurityException e) {
        throw new RuntimeException(e);
      }
    } else {
      entityType = (Class) genType.getActualTypeArguments()[0];
    }
   
    return processToMany(metaClass, t, field, colName, entityType, fieldForKey);
  }
 
  @SuppressWarnings("unchecked")
  private MetaField processToMany(MetaClassSingle<?> ownerMeta, DboTableMeta t, Field field, String colName, Class entityType, Field fieldForKey) {
    //at this point we only need to verify that
    //the class referred has the @NoSqlEntity tag so it is picked up by scanner at a later time
    Class<?> theSuperclass = null;
    Class<?> type = entityType;
    //at this point we only need to verify that
    //the class referred has the @NoSqlEntity tag so it is picked up by scanner at a later time
    if(!entityType.isAnnotationPresent(NoSqlEntity.class)) {
      if(!entityType.isAnnotationPresent(NoSqlDiscriminatorColumn.class))
        throw new RuntimeException("type="+entityType+" needs the NoSqlEntity annotation(or a NoSqlDiscriminatorColumn if it is a subclass of an entity)" +
          " since field has *ToOne annotation.  field="+field.getDeclaringClass().getName()+"."+field.getName());
      theSuperclass = findSuperclassWithNoSqlEntity(entityType);
      type = theSuperclass;
      runOtherSuperclassChecks(entityType, field, theSuperclass);   
//      throw new RuntimeException("You have entityType="+entityType.getName()+" so that class needs the NoSqlEntity annotation" +
//          " since field has OneToMany annotation.  field="+field.getDeclaringClass().getName()+"."+field.getName()+" (or your wrote in the wrong entityType??)");
    }
   
    MetaAbstractClass<?> fkMeta = metaInfo.findOrCreate(type);
   
    if(theSuperclass != null) {
      //we need to swap the classMeta to the more specific class meta which may have not been
      //created yet, oh joy...so we findOrCreate and the shell will be filled in when processing
      //that @NoSqlEntity when it scans the subclasses.
      MetaClassInheritance meta = (MetaClassInheritance) fkMeta;
      fkMeta = meta.findOrCreate(entityType, theSuperclass);
    }
   
    if(field.getType().equals(CursorToMany.class)) {
      MetaCursorField metaField = metaCursorProvider.get();
      metaField.setup(t, field, colName, ownerMeta, fkMeta);
      return metaField;
    }
    //field's type must be Map or List right now today
    if(!field.getType().equals(Map.class) && !field.getType().equals(List.class)
        && !field.getType().equals(Set.class) && !field.getType().equals(Collection.class))
      throw new RuntimeException("field="+field+" must be Set, Collection, List or Map since it is annotated with OneToMany");

    MetaToManyField metaField = metaListProvider.get();
    metaField.setup(t, field, colName,  fkMeta, fieldForKey);
   
    return metaField;
  }

  @SuppressWarnings("unchecked")
  public MetaField processEmbedded(DboTableMeta t, Field field) {
    NoSqlEmbedded embedded = field.getAnnotation(NoSqlEmbedded.class);
    Class<?> type = field.getType();
    if(type.equals(List.class)) {
      ParameterizedType genType = (ParameterizedType) field.getGenericType();
      type = (Class<?>) genType.getActualTypeArguments()[0];
    }
   
    String colNameOrig = embedded.columnNamePrefix();
    String colName = field.getName();
    if(!"".equals(colNameOrig))
      colName = colNameOrig;
   
    MetaField metaField;
    if(type.isAnnotationPresent(NoSqlEmbeddable.class)) {
      MetaAbstractClass<?> fkMeta = metaInfo.findOrCreate(type);
      MetaEmbeddedEntity temp = metaEmbeddedProvider.get();
      temp.setup(t, field, colName, fkMeta);
      metaField = temp;
    } else {
      Converter converter = lookupConverter(field, type, null);
      if(converter == null)
        throw new IllegalArgumentException("We found no converters(customer or standard for type="
            +type.getSimpleName()+" and this class is not annotated with " +
                "@NoSqlEmbeddable either.  The field that caused this issue is field="+field);
     
      MetaEmbeddedSimple meta = metaEmbeddedSimpleProvider.get();
      meta.setup(t, field, colName, converter, type);
      metaField = meta;
    }
   
    return metaField;
  }
 
  @SuppressWarnings("unchecked")
  public MetaField processToOne(DboTableMeta t, Field field, String colNameOrig) {
    String colName = field.getName();
    if(!"".equals(colNameOrig))
      colName = colNameOrig;

    boolean isIndexed = false;
    if(field.isAnnotationPresent(NoSqlIndexed.class))
      isIndexed = true;
   
    boolean isPartitionedBy = false;
    if(field.isAnnotationPresent(NoSqlPartitionByThisField.class))
      isPartitionedBy = true;
   
    Class<?> theSuperclass = null;
    Class<?> type = field.getType();
    //at this point we only need to verify that
    //the class referred has the @NoSqlEntity tag so it is picked up by scanner at a later time
    if(!field.getType().isAnnotationPresent(NoSqlEntity.class) &&
        field.getType() != ToOneProvider.class) {
      if(!field.getType().isAnnotationPresent(NoSqlDiscriminatorColumn.class))
        throw new RuntimeException("type="+field.getType()+" needs the NoSqlEntity annotation(or a NoSqlDiscriminatorColumn if it is a subclass of an entity)" +
          " since field has *ToOne annotation.  field="+field.getDeclaringClass().getName()+"."+field.getName());
      theSuperclass = findSuperclassWithNoSqlEntity(field.getType());
      type = theSuperclass;
      runOtherSuperclassChecks(field.getType(), field, theSuperclass);
     
    } else if(field.getType().isAnnotationPresent(NoSqlInheritance.class)){
      throw new IllegalArgumentException("Okay, so here is the deal.  You have a ToOne relationship defined with an " +
          "Abstract class that has N number of subclasses.  I do not know which subclass to create a " +
          "proxy for unless I read yet another row in, BUT you may not want the extra hit so instead, " +
          "you MUST change this field to javax.inject.Provider instead so you can call provider.get() " +
          "at which point I will go to the nosql database and read the row in and the type information " +
          "and create the correct object for this type.  This is a special case, sorry about that.  In summary, all you " +
          "need to do is change this Field="+field+" to='private ToOneProvider<YourType> provider = new ToOneProvider<YourType>() " +
          "and then your getter should just be return provider.get() and the setter should be provider.set(yourInst) and all" +
          "will be fine with the world");
    } else if(field.getType() == ToOneProvider.class) {
      ParameterizedType genType = (ParameterizedType) field.getGenericType();
      type = (Class) genType.getActualTypeArguments()[0];
    }
   
    MetaProxyField metaField = metaProxyProvider.get();
    MetaAbstractClass<?> classMeta = metaInfo.findOrCreate(type);
   
    if(theSuperclass != null) {
      //we need to swap the classMeta to the more specific class meta which may have not been
      //created yet, oh joy...so we findOrCreate and the shell will be filled in when processing
      //that @NoSqlEntity when it scans the subclasses.
      MetaClassInheritance meta = (MetaClassInheritance) classMeta;
      classMeta = meta.findOrCreate(field.getType(), theSuperclass);
    }
   
    metaField.setup(t, field, colName, classMeta, isIndexed, isPartitionedBy);
    return metaField;
  }

  private void runOtherSuperclassChecks(Class entityType, Field field, Class<?> theSuperclass) {
    if(log.isDebugEnabled())
      log.debug("superclass with @NoSqlEntity="+theSuperclass);
    if(theSuperclass == null)
      throw new RuntimeException("type="+entityType+" has a NoSqlDiscriminatorColumn but as we go " +
          "up the superclass tree, none of the classes are annotated with NoSqlEntity, please add that annotation");
    NoSqlInheritance anno = theSuperclass.getAnnotation(NoSqlInheritance.class);
    if(anno == null)
      throw new RuntimeException("type="+entityType+" has a NoSqlDiscriminatorColumn but as we go " +
          "up the superclass tree, none of the classes are annotated with NoSqlInheritance");
    else if(!classExistsInList(anno, entityType))
      throw new RuntimeException("type="+entityType+" has a NoSqlDiscriminatorColumn and has a super class with NoSqlEntity and NoSqlInheritance but is not listed" +
          "in the NoSqlInheritance tag as one of the subclasses to scan.  Please add it");
  }

  @SuppressWarnings("unused")
  private boolean classExistsInList(NoSqlInheritance anno, Class<?> class1) {
    Class[] subclasses = anno.subclassesToScan();
    Class c = null;
    for(Class sub : subclasses) {
      if(sub == class1)
        return true;
    }   
   
    return false;
  }

  private Class findSuperclassWithNoSqlEntity(Class<?> type) {
    if(type.isAnnotationPresent(NoSqlEntity.class))
      return type;
    else if(type == Object.class)
      return null;
    return findSuperclassWithNoSqlEntity(type.getSuperclass());
  }
}
TOP

Related Classes of com.alvazan.orm.impl.meta.scan.ScannerForField

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.