Package siena.gae

Source Code of siena.gae.GaePersistenceManager

/*
* Copyright 2009 Alberto Gimeno <gimenete at gmail.com>
*
*   Licensed under the Apache License, Version 2.0 (the "License");
*   you may not use this file except in compliance with the License.
*   You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
*   Unless required by applicable law or agreed to in writing, software
*   distributed under the License is distributed on an "AS IS" BASIS,
*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*   See the License for the specific language governing permissions and
*   limitations under the License.
*  
*   @author mandubian <pascal.voitot@mandubian.org>
*/
package siena.gae;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import siena.AbstractPersistenceManager;
import siena.ClassInfo;
import siena.ClassInfo.FieldMapKeys;
import siena.Query;
import siena.QueryAggregated;
import siena.SienaException;
import siena.Util;
import siena.core.Many;
import siena.core.Many4PM;
import siena.core.One;
import siena.core.One4PM;
import siena.core.Relation;
import siena.core.RelationMode;
import siena.core.async.PersistenceManagerAsync;
import siena.core.options.QueryOptionFetchType;
import siena.core.options.QueryOptionOffset;
import siena.core.options.QueryOptionPage;
import siena.core.options.QueryOptionState;

import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.QueryResultIterable;
import com.google.appengine.api.datastore.QueryResultList;
import com.google.appengine.api.datastore.Transaction;

public class GaePersistenceManager extends AbstractPersistenceManager {

  protected DatastoreService ds;
  protected PersistenceManagerAsync asyncPm;
  /*
   * properties are not used but keeps it in case of...
   */
  protected Properties props;
 
  public static final String DB = "GAE";

  public void init(Properties p) {
    ds = DatastoreServiceFactory.getDatastoreService();
    props = p;
  }

  public <T> PersistenceManagerAsync async() {
    if(asyncPm==null){
      asyncPm = new GaePersistenceManagerAsync();
      asyncPm.init(props);
    }
    return asyncPm;   
  }

 
  public void beginTransaction(int isolationLevel) {
    ds.beginTransaction();
  }
 
  public void beginTransaction() {
    ds.beginTransaction();
  }

  public void closeConnection() {
    // does nothing
  }

  public void commitTransaction() {
    Transaction txn = ds.getCurrentTransaction();
    txn.commit();
  }

  public void rollbackTransaction() {
    Transaction txn = ds.getCurrentTransaction();
    txn.rollback();
  }
 
  public void delete(Object obj){
    List<Key> keys = new ArrayList<Key>();
   
    Class<?> clazz = obj.getClass();
    ClassInfo info = ClassInfo.getClassInfo(clazz);
   
    if(info.hasAggregator){
      Relation rel = (Relation)Util.readField(obj, info.aggregator);
      if(rel != null && rel.mode == RelationMode.AGGREGATION){
        ClassInfo parentInfo = ClassInfo.getClassInfo(rel.target.getClass());
        Key parentKey = GaeMappingUtils.makeKey(parentInfo, rel.target);
        _deleteSingle(obj, keys, parentKey, parentInfo, (Field)rel.discriminator);
      }else {
        _deleteSingle(obj, keys, null, null, null);
      }
    }else {
      _deleteSingle(obj, keys, null, null, null);
    }
   
    ds.delete(keys);
  }
 
  private void _deleteSingle(Object obj, List<Key> keys, final Key parentKey, final ClassInfo parentInfo, final Field parentField) {
    Class<?> clazz = obj.getClass();
    ClassInfo info = ClassInfo.getClassInfo(clazz);
   
    Key key;   
    if(parentKey==null){
      key = GaeMappingUtils.getKey(obj);
    }else {
      key = GaeMappingUtils.getKeyFromParent(obj, parentKey, parentInfo, parentField);
    }
   
    // cascading on aggregated fields
    if(!info.aggregatedFields.isEmpty()){
      for(Field f: info.aggregatedFields){
        if(ClassInfo.isModel(f.getType())){
          Object aggObj = Util.readField(obj, f);
          _deleteSingle(aggObj, keys, key, info, f);
        }
        else if(ClassInfo.isMany(f)){
          Many<?> lq = (Many<?>)Util.readField(obj, f);
          if(!lq.asList().isEmpty()){
            _deleteMultiple(lq.asQuery().fetchKeys(), keys, key, info, f);
          }
        }
      }
    }
   
    keys.add(key);
  }

  private void _deleteMultiple(Iterable<?> objects, List<Key> keys, final Key parentKey, final ClassInfo parentInfo, final Field parentField) {
    for(Object obj: objects){
      Class<?> clazz = obj.getClass();
      ClassInfo info = ClassInfo.getClassInfo(clazz);
     
      Key key;   
      if(parentKey==null){
        key = GaeMappingUtils.getKey(obj);
      }else {
        key = GaeMappingUtils.getKeyFromParent(obj, parentKey, parentInfo, parentField);
      }
     
      // cascading on aggregated fields
      if(!info.aggregatedFields.isEmpty()){
        for(Field f: info.aggregatedFields){
          if(ClassInfo.isModel(f.getType())){
            Object aggObj = Util.readField(obj, f);
            _deleteSingle(aggObj, keys, key, info, f);
          }
          else if(ClassInfo.isOne(f)){
            One<?> one = (One<?>)Util.readField(obj, f);
            Object target = one.get();
            if(target != null){
              _deleteSingle(target, keys, key, info, f);
            }
          }
          else if(ClassInfo.isMany(f)){
            Many<?> lq = (Many<?>)Util.readField(obj, f);
            if(!lq.asList().isEmpty()){
              _deleteMultiple(lq.asQuery().fetchKeys(), keys, key, info, f);
            }
          }
        }
      }
     
      keys.add(key);
    }
   
  }
 
  public void get(Object obj) {
    Key key = GaeMappingUtils.getKey(obj);
    ClassInfo info = ClassInfo.getClassInfo(obj.getClass());
    try {
      Entity entity = ds.get(key);
      if(entity != null){
        GaeMappingUtils.fillModel(obj, entity);
       
        // related fields (Many<T> management mainly)
        if(!info.ownedFields.isEmpty()){
          mapOwned(obj);
        }
       
        // aggregated management
        if(!info.aggregatedFields.isEmpty()){
          mapAggregated(obj);
        }
       
        // join management
        if(!info.joinFields.isEmpty()){
          mapJoins(obj);
        }
      }
    }
    catch (Exception e) {
      throw new SienaException(e);
    }
  }

  public <T> T getByKey(Class<T> clazz, Object key) {
    Key gKey = GaeMappingUtils.makeKeyFromId(clazz, key);
    ClassInfo info = ClassInfo.getClassInfo(clazz);
    try {
      Entity entity = ds.get(gKey);
      T obj = null;
      if(entity != null){
        obj = Util.createObjectInstance(clazz);
        GaeMappingUtils.fillModelAndKey(obj, entity);
        // related fields (Many<T> management mainly)
        if(!info.ownedFields.isEmpty()){
          mapOwned(obj);
        }
        // aggregated management
        if(!info.aggregatedFields.isEmpty()){
          mapAggregated(obj);
        }
        // join management
        if(!info.joinFields.isEmpty()){
          mapJoins(obj);
        }
      }
      return obj;
    }
    catch(EntityNotFoundException e){
      return null;
    }
    catch (Exception e) {
      throw new SienaException(e);
    }
  }

  public void insert(Object obj) {
    _insertSingle(obj);
  }
   
  private <T> void _insertSingle(T obj) {
    Class<?> clazz = obj.getClass();
    ClassInfo info = ClassInfo.getClassInfo(clazz);
   
    if(info.hasAggregator){
      Relation rel = (Relation)Util.readField(obj, info.aggregator);
      if(rel != null && rel.mode == RelationMode.AGGREGATION){
        ClassInfo parentInfo = ClassInfo.getClassInfo(rel.target.getClass());
        Key parentKey = GaeMappingUtils.makeKey(parentInfo, rel.target);
        _insertSingle(obj, parentKey, rel.target, parentInfo, (Field)rel.discriminator);
      }else {
        _insertSingle(obj, null, null, null, null);
      }
    }else {
      _insertSingle(obj, null, null, null, null);
    }
  }
 
  private <T> void _insertSingle(T obj, final Key parentEntityKey, final Object parentObj,
      final ClassInfo parentInfo, final Field field) {
    Class<?> clazz = obj.getClass();
    ClassInfo info = ClassInfo.getClassInfo(clazz);
    Field idField = info.getIdField();
    final Entity entity;
   
    // first put the entity to have its ID at least!
    if(parentEntityKey==null){
      entity = GaeMappingUtils.createEntityInstance(idField, info, obj);
      GaeMappingUtils.fillEntity(obj, entity);
      ds.put(entity);
      GaeMappingUtils.setIdFromKey(idField, obj, entity.getKey());
    }else {
      entity = GaeMappingUtils.createEntityInstanceFromParent(
          idField, info, obj,
          parentEntityKey, parentInfo, field);
      GaeMappingUtils.fillEntity(obj, entity);
      ds.put(entity);
      GaeMappingUtils.setIdFromKey(idField, obj, entity.getKey());
    }
   
    if(info.hasAggregatedFields){
      Map<Key, Map<Field, List<Object>>> keyMap = new HashMap<Key, Map<Field, List<Object>>>();
      Map<Field, List<Object>> objectMap = new HashMap<Field, List<Object>>();
      keyMap.put(entity.getKey(), objectMap);
      _populateAggregateFieldMap(objectMap, info, obj);
      _insertMultipleMapFromParent(keyMap, Arrays.asList(info));
    }
   
    if(info.hasOwnedFields){
      List<Object> relObjects = new ArrayList<Object>();
      _populateOwnedList(relObjects, info, obj);
     
      // uses save because we don't know if the objects where already saved or not
      save(relObjects);
    }
  }

  private <T> int _insertMultiple(Iterable<T> objects){
    List<Entity> entities = new ArrayList<Entity>();

    for(Object obj:objects){
      Class<?> clazz = obj.getClass();
      ClassInfo info = ClassInfo.getClassInfo(clazz);
     
      if(info.hasAggregator){
        Relation rel = (Relation)Util.readField(obj, info.aggregator);
        if(rel != null && rel.mode == RelationMode.AGGREGATION){
          ClassInfo parentInfo = ClassInfo.getClassInfo(rel.target.getClass());
          Key parentKey = GaeMappingUtils.makeKey(parentInfo, rel.target);
          _insertAddEntity(entities, obj, info, parentKey, parentInfo, (Field)rel.discriminator);
        }else {
          _insertAddEntity(entities, obj, info, null, null, null);
        }
      }else {
        _insertAddEntity(entities, obj, info, null, null, null);
      }
    }
    return _insertPutEntities(entities, objects);
  }
 
  private <T> void _insertAddEntity(final List<Entity> entities, final T obj, final ClassInfo info,
      final Key parentEntityKey, final ClassInfo parentInfo, final Field field){
    if(parentEntityKey==null){
      Field idField = info.getIdField();
      Entity entity = GaeMappingUtils.createEntityInstance(idField, info, obj);
      GaeMappingUtils.fillEntity(obj, entity);
      entities.add(entity);
    }else {
      Field idField = info.getIdField();
      Entity entity = GaeMappingUtils.createEntityInstanceFromParent(
            idField, info, obj,
            parentEntityKey, parentInfo, field);
      GaeMappingUtils.fillEntity(obj, entity);
      entities.add(entity);
    }

  }
 
  private <T> int _insertPutEntities(Iterable<Entity> entities, Iterable<T> objects) {
    List<Key> generatedKeys = ds.put(entities);
   
    int i=0;
    Map<Key, Map<Field, List<Object>>> keyMap = new HashMap<Key, Map<Field, List<Object>>>();
    List<ClassInfo> infos = new ArrayList<ClassInfo>();

    List<Object> relObjects = new ArrayList<Object>();

    for(Object obj:objects){
      Class<?> clazz = obj.getClass();
      ClassInfo info = ClassInfo.getClassInfo(clazz);
      Field idField = info.getIdField();
      GaeMappingUtils.setIdFromKey(idField, obj, generatedKeys.get(i));
     
      // creates the aggregation relation
      //Relation rel = new Relation(RelationMode.AGGREGATION, parentObj, field);
      //Util.setField(obj, info.aggregator, rel);

      if(info.hasAggregatedFields){
        infos.add(info);
        Map<Field, List<Object>> objectMap = new HashMap<Field, List<Object>>();   
        keyMap.put(generatedKeys.get(i), objectMap);
        _populateAggregateFieldMap(objectMap, info, obj);
      }
     
      if(info.hasOwnedFields){
        _populateOwnedList(relObjects, info, obj);       
      }
      i++;
    }
    if(!keyMap.isEmpty()){
      _insertMultipleMapFromParent(keyMap, infos);
    }
    if(!relObjects.isEmpty()){
      // uses save because we don't know if the objects where already saved or not
      save(relObjects);
    }
    return generatedKeys.size();
  }
 
  private int _insertMultipleMapFromParent(final Map<Key, Map<Field, List<Object>>> keyMap, final List<ClassInfo> parentInfos) {
    List<Entity> entities = new ArrayList<Entity>();
    int i=0;
    for(Key key:keyMap.keySet()){
      Map<Field, List<Object>> objectMap = keyMap.get(key);
      for(Field field: objectMap.keySet()){
        for(Object obj:objectMap.get(field)){
          Class<?> clazz = obj.getClass();
          ClassInfo info = ClassInfo.getClassInfo(clazz);
          Field idField = info.getIdField();
          Entity entity = GaeMappingUtils.createEntityInstanceFromParent(idField, info, obj, key, parentInfos.get(i), field);
          GaeMappingUtils.fillEntity(obj, entity);
          entities.add(entity);
        }
      }
      i++;
    }
   
    List<Key> generatedKeys = ds.put(entities);
   
    i=0;
    Map<Key, Map<Field, List<Object>>> recKeyMap = new HashMap<Key, Map<Field, List<Object>>>();
    List<ClassInfo> recInfos = new ArrayList<ClassInfo>();
    for(Key key:keyMap.keySet()){
      Map<Field, List<Object>> objectMap = keyMap.get(key);
      for(Field field: objectMap.keySet()){
        for(Object obj:objectMap.get(field)){
          Class<?> clazz = obj.getClass();
          ClassInfo info = ClassInfo.getClassInfo(clazz);
          Field idField = info.getIdField();
          GaeMappingUtils.setIdFromKey(idField, obj, generatedKeys.get(i));
         
          if(info.hasAggregatedFields){
            recInfos.add(info);
            Map<Field, List<Object>> recObjectMap = new HashMap<Field, List<Object>>();   
            recKeyMap.put(generatedKeys.get(i), recObjectMap);
            _populateAggregateFieldMap(recObjectMap, info, obj);
          }
         
          i++;
        }
      }
    }
    if(!recKeyMap.isEmpty()){
      _insertMultipleMapFromParent(recKeyMap, recInfos);
    }
    return generatedKeys.size();
  }
 
  public void _populateAggregateFieldMap(Map<Field, List<Object>> map, ClassInfo info, Object obj){
    for(Field f: info.aggregatedFields){
      if(ClassInfo.isOne(f)){
        One4PM<?> one = (One4PM<?>)Util.readField(obj, f);
        Object oneObj = one.get();
        if(oneObj != null){
          map.put(f, (List<Object>)Arrays.asList(oneObj));
        }
        // resets flag anyway
        one.setModified(false);
      }
      else if(ClassInfo.isMany(f)){
        Many4PM<?> lq = (Many4PM<?>)Util.readField(obj, f);
        if(!lq.asList().isEmpty()){
          map.put(f, new ArrayList<Object>((List<Object>)lq.asList2Add()));
          // clears list2adds
          lq.asList2Add().clear();
        }
      }
    }
  }
 
  public void _populateOwnedList(List<Object> relObjects, ClassInfo info, Object obj){
    for(Field f: info.ownedFields){
      if(ClassInfo.isOne(f)){
        // set the owner field in the child object using the content of the one
        One<?> relObj = (One<?>)Util.readField(obj, f);
        Map<FieldMapKeys, Object> m = info.oneFieldMap.get(f);
        if(m != null){
          Field asField = (Field)m.get(FieldMapKeys.FIELD);
          Object oneObj = relObj.get();
          if(oneObj != null){
            Util.setField(oneObj, asField, obj);
            relObjects.add(oneObj);
          }
        }
      }
      else if(ClassInfo.isMany(f)){
        Many4PM<?> lq = (Many4PM<?>)Util.readField(obj, f);
        if(!lq.asList().isEmpty()){
          Field asField = (Field)info.manyFieldMap.get(f).get(FieldMapKeys.FIELD);
          for(Object relObj: lq.asList2Add()){
            Util.setField(relObj, asField, obj);
            relObjects.add(relObj);
          }
          lq.asList2Add().clear();
        }
      }
    }
  }

 
  public void update(Object obj) {
    Class<?> clazz = obj.getClass();
    ClassInfo info = ClassInfo.getClassInfo(clazz);
   
    if(info.hasAggregator){
      Relation rel = (Relation)Util.readField(obj, info.aggregator);
      if(rel != null && rel.mode == RelationMode.AGGREGATION){
        ClassInfo parentInfo = ClassInfo.getClassInfo(rel.target.getClass());
        Key parentKey = GaeMappingUtils.makeKey(parentInfo, rel.target);
        if(!info.hasAggregatedFields && !info.hasOwnedFields){
          _updateSimple(obj, info, parentKey, parentInfo, (Field)rel.discriminator);
        }
        else {
          _updateComplex(obj, parentKey, parentInfo, (Field)rel.discriminator);
        }
      }else {
        if(!info.hasAggregatedFields && !info.hasOwnedFields){
          _updateSimple(obj, info, null, null, null);
        }
        else {
          _updateComplex(obj, null, null, null);
        }
      }
    }else {
      if(!info.hasAggregatedFields && !info.hasOwnedFields){
        _updateSimple(obj, info, null, null, null);
      }
      else {
        _updateComplex(obj, null, null, null);
      }
    }
  }
 
  public enum PersistenceType {
    INSERT,
    UPDATE,
    SAVE,
    DELETE
  }
 
  private <T> void _updateSimple(T obj, ClassInfo info, Key parentKey, ClassInfo parentInfo, Field parentField){
    Entity entity;
    Field idField = info.getIdField();
   
    Object idVal = Util.readField(obj, idField);
    // id with null value means insert
    if(idVal == null){
      if(parentKey==null){
        entity = GaeMappingUtils.createEntityInstance(idField, info, obj);
      }else {
        entity = GaeMappingUtils.createEntityInstanceFromParent(idField, info, obj, parentKey, parentInfo, parentField);
      }
      GaeMappingUtils.fillEntity(obj, entity);
    }else {
      if(parentKey == null){
        entity = GaeMappingUtils.createEntityInstanceForUpdate(info, obj);
      }else {
        entity = GaeMappingUtils.createEntityInstanceForUpdateFromParent(
            info, obj, parentKey, parentInfo, parentField);
      }
      GaeMappingUtils.fillEntity(obj, entity);
    }
   
    if(entity != null){
      ds.put(entity);
    }
  }
 
  private <T> void _updateSimpleMultiple(Iterable<T> objs, Key parentKey, ClassInfo parentInfo, Field parentField){
    List<Entity> entities = new ArrayList<Entity>();
   
    ClassInfo info = null
    Field idField = null;
   
    for(T obj: objs){
      if(info == null){
        info = ClassInfo.getClassInfo(obj.getClass());
        idField = info.getIdField();
      }
     
      Entity entity;
      Object idVal = Util.readField(obj, idField);
      // id with null value means insert
      if(idVal == null){
        if(parentKey==null){
          entity = GaeMappingUtils.createEntityInstance(idField, info, obj);
        }else {
          entity = GaeMappingUtils.createEntityInstanceFromParent(idField, info, obj, parentKey, parentInfo, parentField);
        }
        GaeMappingUtils.fillEntity(obj, entity);
      }else {
        if(parentKey == null){
          entity = GaeMappingUtils.createEntityInstanceForUpdate(info, obj);
        }else {
          entity = GaeMappingUtils.createEntityInstanceForUpdateFromParent(
              info, obj, parentKey, parentInfo, parentField);
        }
        GaeMappingUtils.fillEntity(obj, entity);
      }
     
      entities.add(entity);
    }
   
    if(!entities.isEmpty()){
      ds.put(entities);
    }
  }
 
  private <T> int _updateComplex(T obj, Key parentKey, ClassInfo parentInfo, Field parentField){
    HashMap<PersistenceType, List<Entity>> entitiesMap = new HashMap<PersistenceType, List<Entity>>();
    HashMap<PersistenceType, List<Object>> objectsMap = new HashMap<PersistenceType, List<Object>>();
    HashMap<PersistenceType, List<Key>> keysMap = new HashMap<PersistenceType, List<Key>>();

    _updateBuildMaps(entitiesMap, objectsMap, keysMap, obj, null, null, null);
   
    return _updateManageMaps(entitiesMap, objectsMap, keysMap);
  }

  private <T> int _updateMultiple(Iterable<T> objects){
    HashMap<PersistenceType, List<Entity>> entitiesMap = new HashMap<PersistenceType, List<Entity>>();
    HashMap<PersistenceType, List<Object>> objectsMap = new HashMap<PersistenceType, List<Object>>();
    HashMap<PersistenceType, List<Key>> keysMap = new HashMap<PersistenceType, List<Key>>();

    for(Object obj:objects){
      Class<?> clazz = obj.getClass();
      ClassInfo info = ClassInfo.getClassInfo(clazz);
     
      if(info.hasAggregator){
        Relation rel = (Relation)Util.readField(obj, info.aggregator);
        if(rel != null && rel.mode == RelationMode.AGGREGATION){
          ClassInfo parentInfo = ClassInfo.getClassInfo(rel.target.getClass());
          Key parentKey = GaeMappingUtils.makeKey(parentInfo, rel.target);
          _updateBuildMaps(entitiesMap, objectsMap, keysMap,
              obj, parentKey, parentInfo, (Field)rel.discriminator);
        }else {
          _updateBuildMaps(entitiesMap, objectsMap, keysMap,
              obj, null, null, null);
        }
      }else {
        _updateBuildMaps(entitiesMap, objectsMap, keysMap,
            obj, null, null, null);
      }
    }
    return _updateManageMaps(entitiesMap, objectsMap, keysMap);
  }
 
//  private <T> int _updateComplexMultiple(Iterable<T> objs, Key parentKey, ClassInfo parentInfo, Field parentField){
//    HashMap<PersistenceType, List<Entity>> entitiesMap = new HashMap<PersistenceType, List<Entity>>();
//    HashMap<PersistenceType, List<Object>> objectsMap = new HashMap<PersistenceType, List<Object>>();
//    HashMap<PersistenceType, List<Key>> keysMap = new HashMap<PersistenceType, List<Key>>();
//
//    for(Object obj:objs){
//      _updateBuildMaps(entitiesMap, objectsMap, keysMap, obj, null, null, null);
//    }
//   
//    return _updateManageMaps(entitiesMap, objectsMap, keysMap);
//  }


  //private void _buildUpdateList(List<Entity> entities2Insert, List<Object> objects2Insert, List<Entity> entities2Update, List<Key> entities2Remove, List<Object> objects2Save, Object obj, Key parentKey, ClassInfo parentInfo, Field parentField){
  private static void _updateBuildMaps(
      HashMap<PersistenceType, List<Entity>> entitiesMap,
      HashMap<PersistenceType, List<Object>> objectsMap,
      HashMap<PersistenceType, List<Key>> keysMap,
      Object obj, Key parentKey, ClassInfo parentInfo, Field parentField){
    Class<?> clazz = obj.getClass();
    ClassInfo info = ClassInfo.getClassInfo(clazz);
    Field idField = info.getIdField();
   
    Entity entity;
   
    Object idVal = Util.readField(obj, idField);
    // id with null value means insert
    if(idVal == null){
      if(parentKey==null){
        entity = GaeMappingUtils.createEntityInstance(idField, info, obj);
      }else {
        entity = GaeMappingUtils.createEntityInstanceFromParent(idField, info, obj, parentKey, parentInfo, parentField);
      }
      GaeMappingUtils.fillEntity(obj, entity);   
      List<Entity> entities2Insert = entitiesMap.get(PersistenceType.INSERT);
      if(entities2Insert == null){
        entities2Insert = new ArrayList<Entity>();
        entitiesMap.put(PersistenceType.INSERT, entities2Insert);
      }
      entities2Insert.add(entity);
      List<Object> objects2Insert = objectsMap.get(PersistenceType.INSERT);
      if(objects2Insert == null){
        objects2Insert = new ArrayList<Object>();
        objectsMap.put(PersistenceType.INSERT, objects2Insert);
      }
      objects2Insert.add(obj);
    }else {
      if(parentKey == null){
        entity = GaeMappingUtils.createEntityInstanceForUpdate(info, obj);
      }else {
        entity = GaeMappingUtils.createEntityInstanceForUpdateFromParent(
            info, obj, parentKey, parentInfo, parentField);
      }
      GaeMappingUtils.fillEntity(obj, entity);   
      List<Entity> entities2Update = entitiesMap.get(PersistenceType.UPDATE);
      if(entities2Update == null){
        entities2Update = new ArrayList<Entity>();
        entitiesMap.put(PersistenceType.UPDATE, entities2Update);
      }
      entities2Update.add(entity);
    }

    for(Field f: info.ownedFields){
      // doesn't do anything with One<T>
      if(ClassInfo.isOne(f)){
        // set the owner field in the child object using the content of the one
        One4PM<?> relObj = (One4PM<?>)Util.readField(obj, f);
        Map<FieldMapKeys, Object> m = info.oneFieldMap.get(f);
        if(m != null){
          Field asField = (Field)m.get(FieldMapKeys.FIELD);
          if(relObj.isModified()){
            // unassociates previous object
            Object prevObj =relObj.getPrev();
            if(prevObj != null){
              Util.setField(prevObj, asField, null);
            }
            // resets modified flag
            relObj.setModified(false);
            List<Object> objects2Save = objectsMap.get(PersistenceType.SAVE);
            if(objects2Save == null){
              objects2Save = new ArrayList<Object>();
              objectsMap.put(PersistenceType.SAVE, objects2Save);
            }
            objects2Save.add(prevObj);
           
            Object oneObj = relObj.get();
            if(oneObj != null){
              Util.setField(oneObj, asField, obj);
              objects2Save.add(oneObj);
            }
          }         
        }
      }else if(ClassInfo.isMany(f)){
        Many4PM<?> lq = (Many4PM<?>)Util.readField(obj, f);
       
        // when you remove an element from a Many<T>, it just removes the link
        if(!lq.asList2Remove().isEmpty()){
          Field asField = (Field)info.manyFieldMap.get(f).get(FieldMapKeys.FIELD);
          for(Object elt : lq.asList2Remove()){
            Util.setField(elt, asField, null);
            List<Object> objects2Save = objectsMap.get(PersistenceType.SAVE);
            if(objects2Save == null){
              objects2Save = new ArrayList<Object>();
              objectsMap.put(PersistenceType.SAVE, objects2Save);
            }
            objects2Save.add(elt);
          }         
          lq.asList2Remove().clear();
        }
        if(!lq.asList2Add().isEmpty()){
          Field asField = (Field)info.manyFieldMap.get(f).get(FieldMapKeys.FIELD);
          for(Object elt : lq.asList2Add()){
            Util.setField(elt, asField, obj);
            List<Object> objects2Save = objectsMap.get(PersistenceType.SAVE);
            if(objects2Save == null){
              objects2Save = new ArrayList<Object>();
              objectsMap.put(PersistenceType.SAVE, objects2Save);
            }
            objects2Save.add(elt);
          }
          lq.asList2Add().clear();
        }
      }
    }     
   
    for(Field f: info.aggregatedFields){
      if(ClassInfo.isOne(f)){
        One4PM<?> one = (One4PM<?>)Util.readField(obj, f);
        if(one.isModified()){
          // deletes previous object
          Object prevObj =one.getPrev();
          if(prevObj != null){
            Class<?> delClazz = prevObj.getClass();
            ClassInfo delInfo = ClassInfo.getClassInfo(delClazz);

            Key delKey = GaeMappingUtils.makeKeyFromParent(
                  delInfo, prevObj, entity.getKey(), info, f);
           
            List<Key> key2Remove = keysMap.get(PersistenceType.DELETE);
            if(key2Remove == null){
              key2Remove = new ArrayList<Key>();
              keysMap.put(PersistenceType.DELETE, key2Remove);
            }
            key2Remove.add(delKey);
          }
          // resets modified flag
          one.setModified(false);
         
          Object oneObj = one.get();
          if(oneObj != null){
            _updateBuildMaps(entitiesMap, objectsMap, keysMap, oneObj, entity.getKey(), info, f);
          }
        }
       
      }
      else if(ClassInfo.isMany(f)){
        Many4PM<?> lq = (Many4PM<?>)Util.readField(obj, f);
        // do not update all objects, would be crazy :)
        // UPDATE IS THE RESPONSABILITY OF THE CODER
        /*if(!lq.asList().isEmpty()){
          for(Object elt : lq.asList()){
            _buildUpdateList(entities, entities2Remove, objects2Save, elt, entity.getKey(), info, f);
          }         
        }*/
       
        // add to entities2remove child entities that have been removed
        if(!lq.asList2Remove().isEmpty()){
          Key delKey;
          for(Object elt : lq.asList2Remove()){
            Class<?> delClazz = elt.getClass();
            ClassInfo delInfo = ClassInfo.getClassInfo(delClazz);

            delKey = GaeMappingUtils.makeKeyFromParent(
                  delInfo, elt, entity.getKey(), info, f);
           
            List<Key> key2Remove = keysMap.get(PersistenceType.DELETE);
            if(key2Remove == null){
              key2Remove = new ArrayList<Key>();
              keysMap.put(PersistenceType.DELETE, key2Remove);
            }
            key2Remove.add(delKey);
          }
          lq.asList2Remove().clear();
        }
        if(!lq.asList2Add().isEmpty()){
          for(Object elt : lq.asList2Add()){
            _updateBuildMaps(entitiesMap, objectsMap, keysMap, elt, entity.getKey(), info, f);
          }
          lq.asList2Add().clear();
        }
      }
    }
  }
 
 
  private int _updateManageMaps(
      HashMap<PersistenceType, List<Entity>> entitiesMap,
      HashMap<PersistenceType, List<Object>> objectsMap,
      HashMap<PersistenceType, List<Key>> keysMap){

    int nb = 0;
    // saves the updated owned objects
    List<Object> objs = objectsMap.get(PersistenceType.SAVE);
    if(objs!=null && !objs.isEmpty()){
      nb += save(objs);     
    }
   
    // saves the updated aggregated objects
    List<Entity> entities = entitiesMap.get(PersistenceType.INSERT);
    if(entities!=null && !entities.isEmpty()){
      List<Key> generatedKeys = ds.put(entities);
     
      int i=0;
      for(Object elt:objectsMap.get(PersistenceType.INSERT)){
        Class<?> clazz = elt.getClass();
        ClassInfo info = ClassInfo.getClassInfo(clazz);
        Field idField = info.getIdField();
        GaeMappingUtils.setIdFromKey(idField, elt, generatedKeys.get(i));
      }
     
      nb += generatedKeys.size();
    }

    // saves the updated aggregated objects
    entities = entitiesMap.get(PersistenceType.UPDATE);
    if(entities!=null && !entities.isEmpty()){
      ds.put(entitiesMap.get(PersistenceType.UPDATE));
     
      nb += entities.size();
    }

    // removes the deleted aggregated objects
    List<Key> keys = keysMap.get(PersistenceType.DELETE);
    if(keys!=null && !keys.isEmpty()){
      ds.delete(keys);
     
      nb += keys.size();
    }
   
    return nb;
  }
 
 
  public void save(Object obj) {
    Class<?> clazz = obj.getClass();
    ClassInfo info = ClassInfo.getClassInfo(clazz);
    Field idField = info.getIdField();
   
    //Entity entity;
    Object idVal = Util.readField(obj, idField);
    // id with null value means insert
    if(idVal == null){
      insert(obj);
    }
    // id with not null value means update
    else{
      update(obj);
    }
  }
 
  protected DatastoreService getDatastoreService() {
    return ds;
  }



 
  private <T> PreparedQuery prepare(Query<T> query) {
    Class<?> clazz = query.getQueriedClass();
    ClassInfo info = ClassInfo.getClassInfo(clazz);
    com.google.appengine.api.datastore.Query q;

    // manages aggregation at first
    List<QueryAggregated> aggregs = query.getAggregatees();
    if(aggregs.isEmpty()){
      q = new com.google.appengine.api.datastore.Query(info.tableName);
      return ds.prepare(GaeQueryUtils.addFiltersOrders(query, q));
    }
    else if(aggregs.size() == 1){
      QueryAggregated aggreg = aggregs.get(0);
      Key ancestorKey = GaeMappingUtils.getKey(aggreg.aggregator);
      q = new com.google.appengine.api.datastore.Query(
          GaeMappingUtils.getKindWithAncestorField(info,
              ClassInfo.getClassInfo(aggreg.aggregator.getClass()), aggreg.field));
      q.setAncestor(ancestorKey);
     
      return ds.prepare(GaeQueryUtils.addFiltersOrders(query, q, ancestorKey));
    }
    else {
      throw new SienaException("Only one aggregation per query allowed");
    }
   
  }

  private <T> PreparedQuery prepareKeysOnly(Query<T> query) {
    Class<?> clazz = query.getQueriedClass();
    ClassInfo info = ClassInfo.getClassInfo(clazz);
    com.google.appengine.api.datastore.Query q;

    // manages aggregation at first
    List<QueryAggregated> aggregs = query.getAggregatees();
    if(aggregs.isEmpty()){
      q = new com.google.appengine.api.datastore.Query(ClassInfo.getClassInfo(clazz).tableName);
    }
    else if(aggregs.size() == 1){
      QueryAggregated aggreg = aggregs.get(0);
     
      q = new com.google.appengine.api.datastore.Query(
          GaeMappingUtils.getKindWithAncestorField(info,
              ClassInfo.getClassInfo(aggreg.aggregator.getClass()), aggreg.field));
      q.setAncestor(GaeMappingUtils.getKey(aggreg.aggregator));
    }
    else {
      throw new SienaException("Only one aggregation per query allowed");
    }
   
    return ds.prepare(GaeQueryUtils.addFiltersOrders(query, q).setKeysOnly());
  }

 
  protected <T> T mapJoins(Query<T> query, T model) {
    try {
      // join queries
      Map<Field, ArrayList<Key>> fieldMap = GaeQueryUtils.buildJoinFieldKeysMap(query);
     
      // creates the list of joined entity keys to extract
      for(Field field: fieldMap.keySet()){
        Key key = GaeMappingUtils.getKey(field.get(model));
        List<Key> keys = fieldMap.get(field);
        if(!keys.contains(key))
          keys.add(key);
      }
     
      Map<Field, Map<Key, Entity>> entityMap =
        new HashMap<Field, Map<Key, Entity>>();

      try {
        // retrieves all joined entities per field
        for(Field field: fieldMap.keySet()){
          Map<Key, Entity> entities = ds.get(fieldMap.get(field));
          entityMap.put(field, entities);
        }
      }catch(Exception ex){
        throw new SienaException(ex);
      }
      // associates linked models to their models
      // linkedModels is just a map to contain entities already mapped
      Map<Key, Object> linkedModels = new HashMap<Key, Object>();
      Object linkedObj;
      Entity entity;
     
      for(Field field: fieldMap.keySet()){
        Object objVal = field.get(model);
        Key key = GaeMappingUtils.getKey(objVal);
        linkedObj = linkedModels.get(key);
        if(linkedObj==null){
          entity = entityMap.get(field).get(key);
          linkedObj = objVal;
          GaeMappingUtils.fillModel(linkedObj, entity);
          linkedModels.put(key, linkedObj);
        }
     
        field.set(model, linkedObj);       
      }

      return model;
    } catch(IllegalAccessException ex){
      throw new SienaException(ex);
    }   
  }
 
  protected <T> T mapJoins(T model) {
    try {
      // join queries
      Map<Field, ArrayList<Key>> fieldMap = GaeQueryUtils.buildJoinFieldKeysMap(model);
     
      // creates the list of joined entity keys to extract
      for(Field field: fieldMap.keySet()){
        Key key = GaeMappingUtils.getKey(field.get(model));
        List<Key> keys = fieldMap.get(field);
        if(!keys.contains(key))
          keys.add(key);
      }
     
      Map<Field, Map<Key, Entity>> entityMap =
        new HashMap<Field, Map<Key, Entity>>();

      try {
        // retrieves all joined entities per field
        for(Field field: fieldMap.keySet()){
          Map<Key, Entity> entities = ds.get(fieldMap.get(field));
          entityMap.put(field, entities);
        }
      }catch(Exception ex){
        throw new SienaException(ex);
      }
      // associates linked models to their models
      // linkedModels is just a map to contain entities already mapped
      Map<Key, Object> linkedModels = new HashMap<Key, Object>();
      Object linkedObj;
      Entity entity;
     
      for(Field field: fieldMap.keySet()){
        Object objVal = field.get(model);
        Key key = GaeMappingUtils.getKey(objVal);
        linkedObj = linkedModels.get(key);
        if(linkedObj==null){
          entity = entityMap.get(field).get(key);
          linkedObj = objVal;
          GaeMappingUtils.fillModel(linkedObj, entity);
          linkedModels.put(key, linkedObj);
        }
     
        field.set(model, linkedObj);       
      }

      return model;
    } catch(IllegalAccessException ex){
      throw new SienaException(ex);
    }   
  }
 
  protected <T> List<T> mapJoins(Query<T> query, List<T> models) {
    try {
      // join queries
      Map<Field, ArrayList<Key>> fieldMap = GaeQueryUtils.buildJoinFieldKeysMap(query);
     
      // creates the list of joined entity keys to extract
      for (final T model : models) {
        for(Field field: fieldMap.keySet()){
                    Object objVal = Util.readField(model, field);
                    // our object is not linked to another object...so it doesn't have any key
                    if(objVal == null) {
                        continue;
                    }

                    Key key = GaeMappingUtils.getKey(objVal);
          List<Key> keys = fieldMap.get(field);
          if(!keys.contains(key))
            keys.add(key);
        }
      }
     
      Map<Field, Map<Key, Entity>> entityMap =
        new HashMap<Field, Map<Key, Entity>>();

      try {
        // retrieves all joined entities per field
        for(Field field: fieldMap.keySet()){
          Map<Key, Entity> entities = ds.get(fieldMap.get(field));
          // gets the future here because we need it so we wait for it
          entityMap.put(field, entities);
        }
      }catch(Exception ex){
        throw new SienaException(ex);
      }
      // associates linked models to their models
      // linkedModels is just a map to contain entities already mapped
      Map<Key, Object> linkedModels = new HashMap<Key, Object>();
      Object linkedObj;
      Entity entity;
     
      for (final T model : models) {
        for(Field field: fieldMap.keySet()){
          Object objVal = Util.readField(model, field);
                    // our object is not linked to another object...so it doesn't have any key
                    if(objVal == null) {
                        continue;
                    }

          Key key = GaeMappingUtils.getKey(objVal);
          linkedObj = linkedModels.get(key);
          if(linkedObj==null){
            entity = entityMap.get(field).get(key);
            linkedObj = objVal;
            GaeMappingUtils.fillModel(linkedObj, entity);
            linkedModels.put(key, linkedObj);
          }
       
          field.set(model, linkedObj);       
        }
      }
      return models;
    } catch(IllegalAccessException ex){
      throw new SienaException(ex);
    }   
  }
 
  protected <T> List<T> mapJoins(List<T> models) {
    try {
      // join queries
      Map<Field, ArrayList<Key>> fieldMap = null;
     
      // creates the list of joined entity keys to extract
      for (final T model : models) {
        // initializes fieldMap
        if(fieldMap == null){
          fieldMap = GaeQueryUtils.buildJoinFieldKeysMap(model);
        }
        for(Field field: fieldMap.keySet()){
                    Object objVal = Util.readField(model, field);
                    // our object is not linked to another object...so it doesn't have any key
                    if(objVal == null) {
                        continue;
                    }

                    Key key = GaeMappingUtils.getKey(objVal);
          List<Key> keys = fieldMap.get(field);
          if(!keys.contains(key))
            keys.add(key);
        }
      }
     
      Map<Field, Map<Key, Entity>> entityMap =
        new HashMap<Field, Map<Key, Entity>>();

      try {
        // retrieves all joined entities per field
        for(Field field: fieldMap.keySet()){
          Map<Key, Entity> entities = ds.get(fieldMap.get(field));
          // gets the future here because we need it so we wait for it
          entityMap.put(field, entities);
        }
      }catch(Exception ex){
        throw new SienaException(ex);
      }
      // associates linked models to their models
      // linkedModels is just a map to contain entities already mapped
      Map<Key, Object> linkedModels = new HashMap<Key, Object>();
      Object linkedObj;
      Entity entity;
     
      for (final T model : models) {
        for(Field field: fieldMap.keySet()){
          Object objVal = Util.readField(model, field);
                    // our object is not linked to another object...so it doesn't have any key
                    if(objVal == null) {
                        continue;
                    }

          Key key = GaeMappingUtils.getKey(objVal);
          linkedObj = linkedModels.get(key);
          if(linkedObj==null){
            entity = entityMap.get(field).get(key);
            linkedObj = objVal;
            GaeMappingUtils.fillModel(linkedObj, entity);
            linkedModels.put(key, linkedObj);
          }
       
          field.set(model, linkedObj);       
        }
      }
      return models;
    } catch(IllegalAccessException ex){
      throw new SienaException(ex);
    }   
  }
 
  protected <T> void fillAggregated(ClassInfo info, T ancestor, Key ancestorKey) {
    // now gets aggregated one2one (one2many are retrieved by ListQuery except with @Join)
    for(Field f:info.aggregatedFields){
      Class<?> cClazz = f.getType();
      ClassInfo cInfo = ClassInfo.getClassInfo(cClazz);
      if(ClassInfo.isModel(cClazz)){
        // creates a query for fieldname:child_tablename
        com.google.appengine.api.datastore.Query q =
          new com.google.appengine.api.datastore.Query(GaeMappingUtils.getKindWithAncestorField(cInfo, info, f));

        PreparedQuery pq = ds.prepare(q.setAncestor(ancestorKey));
        Entity cEntity = pq.asSingleEntity();
        Object cObj = Util.createObjectInstance(cClazz);
        GaeMappingUtils.fillModelAndKey(cObj, cEntity);
        Util.setField(ancestor, f, cObj);
      }
      // todo manage joined one2many listquery
      else if(ClassInfo.isMany(f)){
        Many4PM<?> lq = (Many4PM<?>)Util.readField(ancestor, f);
        // sets the sync flag to false to tell that it should be fetched when the listquery is accessed!
        lq.setSync(false);
      }
    }
  }
 
  protected <T> T mapOwned(T model) {
    Class<?> clazz = model.getClass();
    ClassInfo info = ClassInfo.getClassInfo(clazz);
   
    for(Field f: info.ownedFields){
      if(ClassInfo.isOne(f)){
        One4PM<?> lq = (One4PM<?>)Util.readField(model, f);
        // sets the sync flag to false to tell that it should be fetched when the listquery is accessed!
        lq.setSync(false);
      }
      else if(ClassInfo.isMany(f)){
        Many4PM<?> lq = (Many4PM<?>)Util.readField(model, f);
        // sets the sync flag to false to tell that it should be fetched when the listquery is accessed!
        lq.setSync(false);
      }
    }
   
    return model;
  }

  protected <T> List<T> mapOwned(List<T> models) {
    for (final T model : models) {
      mapOwned(model);
    }
   
    return models;
  }
 
  protected <T> T mapAggregated(T model) {
    Class<?> clazz = model.getClass();
    ClassInfo info = ClassInfo.getClassInfo(clazz);
   
    //Map<Field, ClassInfo> modelMap = new HashMap<Field, ClassInfo>();
    //boolean hasOneMany = false;
   
    // we scan the aggregatedfields to find potential one/many
    // if there is a listquery, we don't try to use the KINDLESS request
    // because we don't know how many children the entity can have.
    // if there is NO listquery, we use the kindless request
    for(Field f: info.aggregatedFields){
      //Class<?> fClazz = f.getType();
      //ClassInfo fInfo = ClassInfo.getClassInfo(fClazz);
      if(ClassInfo.isOne(f)){
        One4PM<?> one = (One4PM<?>)Util.readField(model, f);
        // sets the sync flag to false to tell that it should be fetched when the one is accessed!
        one.setSync(false);
       
        //hasOneMany = true;
      }
      else if(ClassInfo.isMany(f)){
        Many4PM<?> lq = (Many4PM<?>)Util.readField(model, f);
        // sets the sync flag to false to tell that it should be fetched when the many is accessed!
        lq.setSync(false);
       
        //hasOneMany = true;
      }
    }
     
    /*if(!hasOneMany){
      // creates a kindless query to retrieve all subentities at once.
      com.google.appengine.api.datastore.Query q =
        new com.google.appengine.api.datastore.Query();
      Key parentKey = GaeMappingUtils.getKey(model);
     
      q.setAncestor(parentKey);
      // this removes the parent from query
      q.addFilter(Entity.KEY_RESERVED_PROPERTY,
          com.google.appengine.api.datastore.Query.FilterOperator.GREATER_THAN,
            parentKey);
     
      PreparedQuery pq = ds.prepare(q);
      List<Entity> childEntities = pq.asList(FetchOptions.Builder.withDefaults());
     
      for(Field f: modelMap.keySet()){
        Class<?> fClazz = f.getType();
        ClassInfo fInfo = modelMap.get(f);
        String kind = GaeMappingUtils.getKindWithAncestorField(fInfo, info, f);
        Entity found = null;
        for(Entity e: childEntities){
          if(kind.equals(e.getKind())){
            found = e;
            childEntities.remove(e);
            break;
          }
        }
         
        if(found != null){
          Object fObj = GaeMappingUtils.mapEntity(found, fClazz);
          Util.setField(model, f, fObj);
        }
      }
    } 
    else {
      for(Field f: modelMap.keySet()){
        Class<?> fClazz = f.getType();
        ClassInfo fInfo = modelMap.get(f);
        String kind = GaeMappingUtils.getKindWithAncestorField(fInfo, info, f);
        com.google.appengine.api.datastore.Query q =
          new com.google.appengine.api.datastore.Query(kind);
        Key parentKey = GaeMappingUtils.getKey(model);
        q.setAncestor(parentKey);
        PreparedQuery pq = ds.prepare(q);
        Entity childEntity = pq.asSingleEntity();
        Object fObj = GaeMappingUtils.mapEntity(childEntity, fClazz);
        Util.setField(model, f, fObj);
      }
    }*/
    return model;
  }
 

 
  protected <T> List<T> mapAggregated(List<T> models) {
    for (final T model : models) {
      mapAggregated(model);
    }
   
    return models;
  }

 
  protected <T> T map(Query<T> query, Entity entity) {
    Class<T> clazz = query.getQueriedClass();
    T result = GaeMappingUtils.mapEntity(entity, clazz);
    ClassInfo info = ClassInfo.getClassInfo(clazz);
   
    // maps model relations to be able to associate children to aggregators
    GaeMappingUtils.mapRelation(query, result, info);
   
    // related fields (Many<T> management mainly)
    if(!info.ownedFields.isEmpty()){
      mapOwned(result);
    }
   
    // aggregated management
    if(!ClassInfo.getClassInfo(clazz).aggregatedFields.isEmpty()){
      mapAggregated(result);
    }
   
    // join management
    if(!query.getJoins().isEmpty() || !ClassInfo.getClassInfo(clazz).joinFields.isEmpty())
      mapJoins(query, result);
   
    return result;
  }
 
  protected <T> List<T> map(Query<T> query, List<Entity> entities) {
    Class<T> clazz = query.getQueriedClass();
    List<T> results = GaeMappingUtils.mapEntities(entities, clazz);
    ClassInfo info = ClassInfo.getClassInfo(clazz);
   
    // maps model relations to be able to associate children to aggregators
    GaeMappingUtils.mapRelations(query, results, info);
   
    // related fields (Many<T> management mainly)
    if(!info.ownedFields.isEmpty()){
      mapOwned(results);
    }
   
    // aggregated management
    if(!info.aggregatedFields.isEmpty()){
      mapAggregated(results);
    }

    // join management
    if(!query.getJoins().isEmpty() || !info.joinFields.isEmpty())
      mapJoins(query, results);
   
    return results;
  }

  protected <T> List<T> mapKeysOnly(Query<T> query, List<Entity> entities) {
    Class<T> clazz = query.getQueriedClass();
    List<T> results = GaeMappingUtils.mapEntitiesKeysOnly(entities, clazz);
    ClassInfo info = ClassInfo.getClassInfo(clazz);
   
    // maps model relations to be able to associate children to aggregators
    GaeMappingUtils.mapRelations(query, results, info);
   
    // DOESN'T MANAGE OWNED/AGGREGATED/JOIN fields
    return results;
  }

 
  private <T> List<T> doFetchList(Query<T> query, int limit, int offset) {
    QueryOptionGaeContext gaeCtx = (QueryOptionGaeContext)query.option(QueryOptionGaeContext.ID);
    if(gaeCtx==null){
      gaeCtx = new QueryOptionGaeContext();
      query.customize(gaeCtx);
    }
   
    QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID);
    QueryOptionFetchType fetchType = (QueryOptionFetchType)query.option(QueryOptionFetchType.ID);
    FetchOptions fetchOptions = FetchOptions.Builder.withDefaults();

    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
    if(!pag.isPaginating()){
      // no pagination but pageOption active
      if(pag.isActive()){
        // if local limit is set, it overrides the pageOption.pageSize
        if(limit!=Integer.MAX_VALUE){
          gaeCtx.realPageSize = limit;
          fetchOptions.limit(gaeCtx.realPageSize);
          // pageOption is passivated to be sure it is not reused
          pag.passivate();
        }
        // using pageOption.pageSize
        else {
          gaeCtx.realPageSize = pag.pageSize;
          fetchOptions.limit(gaeCtx.realPageSize);
          // passivates the pageOption in stateless mode not to keep anything between 2 requests
          if(state.isStateless()){
            pag.passivate();
          }           
        }
      }
      else {
        if(limit != Integer.MAX_VALUE){
          gaeCtx.realPageSize = limit;
          fetchOptions.limit(gaeCtx.realPageSize);
        }
      }
    }else {
      // paginating so use the pagesize and don't passivate pageOption
      // local limit is not taken into account
      gaeCtx.realPageSize = pag.pageSize;
      fetchOptions.limit(gaeCtx.realPageSize);
    }

    QueryOptionOffset off = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
    // if local offset has been set, uses it
    if(offset!=0){
      off.activate();
      off.offset = offset;
    }
           
    // if previousPage has detected there is no more data, simply returns an empty list
    if(gaeCtx.noMoreDataBefore){
      return new ArrayList<T>();
    }
           
    if(state.isStateless()) {
      if(pag.isPaginating()){
        if(off.isActive()){
          gaeCtx.realOffset+=off.offset;
          fetchOptions.offset(gaeCtx.realOffset);
          off.passivate();
        }else {
          fetchOptions.offset(gaeCtx.realOffset);
        }
      }else {
        // if stateless and not paginating, resets the realoffset to 0
        gaeCtx.realOffset = 0;
        if(off.isActive()){
          gaeCtx.realOffset=off.offset;
          fetchOptions.offset(gaeCtx.realOffset);
          off.passivate();
        }
      }
     
      switch(fetchType.fetchType){
      case KEYS_ONLY:
        {
          // uses iterable as it is the only async request for prepared query for the time being
          List<Entity> entities = prepareKeysOnly(query).asList(fetchOptions);
          // if paginating and 0 results then no more data else resets noMoreDataAfter
          if(pag.isPaginating()){
            if(entities.size() == 0){
              gaeCtx.noMoreDataAfter = true;
            }
            else {
              gaeCtx.noMoreDataAfter = false;
            }
          }
          return mapKeysOnly(query, entities);
        }
      case NORMAL:
      default:
        {
          // uses iterable as it is the only async request for prepared query for the time being
          List<Entity> entities = prepare(query).asList(fetchOptions);
          // if paginating and 0 results then no more data else resets noMoreDataAfter
          if(pag.isPaginating()){
            if(entities.size() == 0){
              gaeCtx.noMoreDataAfter = true;
            }
            else {
              gaeCtx.noMoreDataAfter = false;
            }
          }
          return map(query, entities);
        }
      }

    }else {
      if(off.isActive()){
        // by default, we add the offset but it can be added with the realoffset
        // in case of cursor desactivated
        fetchOptions.offset(off.offset);
        gaeCtx.realOffset+=off.offset;
        off.passivate();
      }
     
      // manages cursor limitations for IN and != operators with offsets
      if(!gaeCtx.isActive()){
        // cursor not yet created
        switch(fetchType.fetchType){
        case KEYS_ONLY:
          {
            PreparedQuery pq = prepareKeysOnly(query);
            if(!gaeCtx.useCursor){
              // then uses offset (in case of IN or != operators)
              //if(offset.isActive()){
              //  fetchOptions.offset(gaeCtx.realOffset);
              //}           
              fetchOptions.offset(gaeCtx.realOffset);
            }
           
            // we can't use real asynchronous function with cursors
            // so the page is extracted at once and wrapped into a SienaFuture
            QueryResultList<Entity> entities = pq.asQueryResultList(fetchOptions);

            // activates the GaeCtx now that it is initialised
            gaeCtx.activate();
            // sets the current cursor (in stateful mode, cursor is always kept for further use)
            if(pag.isPaginating()){
              Cursor cursor = entities.getCursor();
              if(cursor!=null){
                gaeCtx.addCursor(cursor.toWebSafeString());
              }
             
              // if paginating and 0 results then no more data else resets noMoreDataAfter
              if(entities.size()==0){
                gaeCtx.noMoreDataAfter = true;
              } else {
                gaeCtx.noMoreDataAfter = false;
              }
            }else{
              Cursor cursor = entities.getCursor();
              if(cursor!=null){
                gaeCtx.addAndMoveCursor(entities.getCursor().toWebSafeString());
              }
              // keeps track of the offset anyway if not paginating
              gaeCtx.realOffset+=entities.size();
            }                     
           
            return mapKeysOnly(query, entities);
          }
        case NORMAL:
        default:
          {
            PreparedQuery pq = prepare(query);
            if(!gaeCtx.useCursor){
              // then uses offset (in case of IN or != operators)
              //if(offset.isActive()){
              //  fetchOptions.offset(gaeCtx.realOffset);
              //}
              fetchOptions.offset(gaeCtx.realOffset);
            }
            // we can't use real asynchronous function with cursors
            // so the page is extracted at once and wrapped into a SienaFuture
            QueryResultList<Entity> entities = pq.asQueryResultList(fetchOptions);
           
            // activates the GaeCtx now that it is initialised
            gaeCtx.activate();
            // sets the current cursor (in stateful mode, cursor is always kept for further use)
            if(pag.isPaginating()){
              Cursor cursor = entities.getCursor();
              if(cursor!=null){
                gaeCtx.addCursor(cursor.toWebSafeString());
              }
              // if paginating and 0 results then no more data else resets noMoreDataAfter
              if(entities.size()==0){
                gaeCtx.noMoreDataAfter = true;
              } else {
                gaeCtx.noMoreDataAfter = false;
              }
            }else{
              Cursor cursor = entities.getCursor();
              if(cursor!=null){
                gaeCtx.addAndMoveCursor(entities.getCursor().toWebSafeString());
              }
              // keeps track of the offset anyway if not paginating
              gaeCtx.realOffset+=entities.size();
            }
           
            return map(query, entities);
          }
        }
       
      }else {
        switch(fetchType.fetchType){
        case KEYS_ONLY:
          {
            // we prepare the query each time
            PreparedQuery pq = prepareKeysOnly(query);
            QueryResultList<Entity> entities;
            if(!gaeCtx.useCursor){
              // then uses offset (in case of IN or != operators)
              //if(offset.isActive()){
              //  fetchOptions.offset(gaeCtx.realOffset);
              //}
              fetchOptions.offset(gaeCtx.realOffset);
              // we can't use real asynchronous function with cursors
              // so the page is extracted at once and wrapped into a SienaFuture
              entities = pq.asQueryResultList(fetchOptions);
            }else {
              // we can't use real asynchronous function with cursors
              // so the page is extracted at once and wrapped into a SienaFuture
              String cursor = gaeCtx.currentCursor();
              if(cursor!=null){
                entities = pq.asQueryResultList(
                  fetchOptions.startCursor(Cursor.fromWebSafeString(cursor)));
              }
              else {
                entities = pq.asQueryResultList(fetchOptions);
              }
            }
           
            // sets the current cursor (in stateful mode, cursor is always kept for further use)
            if(pag.isPaginating()){
              Cursor cursor = entities.getCursor();
              if(cursor!=null){
                gaeCtx.addCursor(cursor.toWebSafeString());
              }
              // if paginating and 0 results then no more data else resets noMoreDataAfter
              if(entities.size()==0){
                gaeCtx.noMoreDataAfter = true;
              } else {
                gaeCtx.noMoreDataAfter = false;
              }
            }else{
              Cursor cursor = entities.getCursor();
              if(cursor!=null){
                gaeCtx.addAndMoveCursor(entities.getCursor().toWebSafeString());
              }
              // keeps track of the offset anyway if not paginating
              gaeCtx.realOffset+=entities.size();
            }
            //}
           
            return mapKeysOnly(query, entities);
          }
        case NORMAL:
        default:
          {
            PreparedQuery pq = prepare(query);
            QueryResultList<Entity> entities;
            if(!gaeCtx.useCursor){
              // then uses offset (in case of IN or != operators)
              //if(offset.isActive()){
              //  fetchOptions.offset(gaeCtx.realOffset);
              //}
             
              fetchOptions.offset(gaeCtx.realOffset);
              // we can't use real asynchronous function with cursors
              // so the page is extracted at once and wrapped into a SienaFuture
              entities = pq.asQueryResultList(fetchOptions);
            }else {
              // we can't use real asynchronous function with cursors
              // so the page is extracted at once and wrapped into a SienaFuture
              String cursor = gaeCtx.currentCursor();
              if(cursor!=null){
                entities = pq.asQueryResultList(
                  fetchOptions.startCursor(Cursor.fromWebSafeString(gaeCtx.currentCursor())));
              }else {
                entities = pq.asQueryResultList(fetchOptions);
              }
            }
           
            // sets the current cursor (in stateful mode, cursor is always kept for further use)
            if(pag.isPaginating()){
              Cursor cursor = entities.getCursor();
              if(cursor!=null){
                gaeCtx.addCursor(cursor.toWebSafeString());
              }
              // if paginating and 0 results then no more data else resets noMoreDataAfter
              if(entities.size()==0){
                gaeCtx.noMoreDataAfter = true;
              } else {
                gaeCtx.noMoreDataAfter = false;
              }
            }else{
              Cursor cursor = entities.getCursor();
              if(cursor!=null){
                gaeCtx.addAndMoveCursor(entities.getCursor().toWebSafeString());
              }
              // keeps track of the offset anyway
              gaeCtx.realOffset+=entities.size();
            }
           
            return map(query, entities);
          }
        }
      }
    }
  }
 
 
  private <T> Iterable<T> doFetchIterable(Query<T> query, int limit, int offset) {
    QueryOptionGaeContext gaeCtx = (QueryOptionGaeContext)query.option(QueryOptionGaeContext.ID);
    QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID);
    QueryOptionFetchType fetchType = (QueryOptionFetchType)query.option(QueryOptionFetchType.ID);
       
    if(gaeCtx==null){
      gaeCtx = new QueryOptionGaeContext();
      query.customize(gaeCtx);
    }

    FetchOptions fetchOptions = FetchOptions.Builder.withDefaults();

    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
    if(!pag.isPaginating()){
      // no pagination but pageOption active
      if(pag.isActive()){
        // if local limit is set, it overrides the pageOption.pageSize
        if(limit!=Integer.MAX_VALUE){
          gaeCtx.realPageSize = limit;
          fetchOptions.limit(gaeCtx.realPageSize);
          // pageOption is passivated to be sure it is not reused
          pag.passivate();
        }
        // using pageOption.pageSize
        else {
          gaeCtx.realPageSize = pag.pageSize;
          fetchOptions.limit(gaeCtx.realPageSize);
          // passivates the pageOption in stateless mode not to keep anything between 2 requests
          if(state.isStateless()){
            pag.passivate();
          }           
        }
      }
      else {
        if(limit != Integer.MAX_VALUE){
          gaeCtx.realPageSize = limit;
          fetchOptions.limit(gaeCtx.realPageSize);
        }
      }
    }else {
      // paginating so use the pagesize and don't passivate pageOption
      // local limit is not taken into account
      gaeCtx.realPageSize = pag.pageSize;
      fetchOptions.limit(gaeCtx.realPageSize);
    }

    QueryOptionOffset off = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
    // if local offset has been set, uses it
    if(offset!=0){
      off.activate();
      off.offset = offset;
    }
   
    // if previousPage has detected there is no more data, simply returns an empty list
    if(gaeCtx.noMoreDataBefore){
      return new ArrayList<T>();
    }
           
    if(state.isStateless()) {
      if(pag.isPaginating()){     
        if(off.isActive()){
          gaeCtx.realOffset+=off.offset;
          fetchOptions.offset(gaeCtx.realOffset);
          off.passivate();
        }else {
          fetchOptions.offset(gaeCtx.realOffset);
        }
      }else {
               
        // if stateless and not paginating, resets the realoffset to 0
        gaeCtx.realOffset = off.offset;
        if(off.isActive()){
          fetchOptions.offset(gaeCtx.realOffset);
          off.passivate();
        }
      }
     
      switch(fetchType.fetchType){
      case ITER:
      default:
        {
          // uses iterable as it is the only async request for prepared query for the time being
          Iterable<Entity> entities = prepare(query).asIterable(fetchOptions);
          return new GaeSienaIterable<T>(this, entities, query);
        }
      }
     
    }else {     
      if(off.isActive()){
        // by default, we add the offset but it can be added with the realoffset
        // in case of cursor desactivated
        fetchOptions.offset(off.offset);
        gaeCtx.realOffset+=off.offset;
        off.passivate();
      }
      // manages cursor limitations for IN and != operators   
      if(!gaeCtx.isActive()){
        // cursor not yet created
        switch(fetchType.fetchType){
        case ITER:
        default:
          {
            PreparedQuery pq = prepare(query);
           
            if(pag.isPaginating()){
              // in case of pagination, we need to allow asynchronous calls such as:
              // QueryAsync<MyClass> query = pm.createQuery(MyClass).paginate(5).stateful().order("name");
              // SienaFuture<Iterable<MyClass>> future1 = query.iter();
              // SienaFuture<Iterable<MyClass>> future2 = query.nextPage().iter();
              // Iterable<MyClass> it = future1.get().iterator();
              // while(it.hasNext()) { // do it }
              // it = future2.get().iterator();
              // while(it.hasNext()) { // do it }
             
              // so we can't use the asQueryResultIterable as the cursor is not moved to the end of the current page
              // but moved at each call of iterable.iterator().next()
              // thus we use the List in this case to be able to move directly to the next page with cursors
              QueryResultList<Entity> entities = pq.asQueryResultList(fetchOptions);

              // activates the GaeCtx now that it is initialised
              gaeCtx.activate();
              // sets the current cursor (in stateful mode, cursor is always kept for further use)
              //if(gaeCtx.useCursor){
              Cursor cursor = entities.getCursor();
              if(cursor!=null){
                gaeCtx.addCursor(cursor.toWebSafeString());
              }
              //}
              return new GaeSienaIterable<T>(this, entities, query);
            }else {
              // if not paginating, we simply use the queryresultiterable and moves the current cursor
              // while iterating
              QueryResultIterable<Entity> entities = pq.asQueryResultIterable(fetchOptions);
              // activates the GaeCtx now that it is initialised
              gaeCtx.activate();
              return new GaeSienaIterableWithCursor<T>(this, entities, query);
            }
           
          }
        }
       
      }else {
        switch(fetchType.fetchType){
        case ITER:
        default:
          {
            PreparedQuery pq = prepare(query);
            if(pag.isPaginating()){
              // in case of pagination, we need to allow asynchronous calls such as:
              // QueryAsync<MyClass> query = pm.createQuery(MyClass).paginate(5).stateful().order("name");
              // SienaFuture<Iterable<MyClass>> future1 = query.iter();
              // SienaFuture<Iterable<MyClass>> future2 = query.nextPage().iter();
              // Iterable<MyClass> it = future1.get().iterator();
              // while(it.hasNext()) { // do it }
              // it = future2.get().iterator();
              // while(it.hasNext()) { // do it }
             
              // so we can't use the asQueryResultIterable as the cursor is not moved to the end of the current page
              // but moved at each call of iterable.iterator().next()
              // thus we use the List in this case to be able to move directly to the next page with cursors
              QueryResultList<Entity> entities;
              if(!gaeCtx.useCursor){
                // then uses offset (in case of IN or != operators)
                //if(offset.isActive()){
                //  fetchOptions.offset(gaeCtx.realOffset);
                //}
                fetchOptions.offset(gaeCtx.realOffset);
                entities = pq.asQueryResultList(fetchOptions);
              }else {
                String cursor = gaeCtx.currentCursor();
                if(cursor!=null){
                  entities = pq.asQueryResultList(
                    fetchOptions.startCursor(Cursor.fromWebSafeString(cursor)));
                }else {
                  entities = pq.asQueryResultList(fetchOptions);
                }
               
                // sets the current cursor (in stateful mode, cursor is always kept for further use)
                //if(gaeCtx.useCursor){
                gaeCtx.addCursor(entities.getCursor().toWebSafeString());
                //}
              }
              return new GaeSienaIterable<T>(this, entities, query);
            }else {
              // if not paginating, we simply use the queryresultiterable and moves the current cursor
              // while iterating
              QueryResultIterable<Entity> entities;
              if(!gaeCtx.useCursor){
                // then uses offset (in case of IN or != operators)
                //if(offset.isActive()){
                //  fetchOptions.offset(gaeCtx.realOffset);
                //}
                fetchOptions.offset(gaeCtx.realOffset);
                entities = pq.asQueryResultIterable(fetchOptions);
              }else {
                String cursor = gaeCtx.currentCursor();
                if(cursor!=null){
                  entities = pq.asQueryResultIterable(
                    fetchOptions.startCursor(Cursor.fromWebSafeString(gaeCtx.currentCursor())));
                }else {
                  entities = pq.asQueryResultIterable(fetchOptions)
                }
              }
              return new GaeSienaIterableWithCursor<T>(this, entities, query);
            }
          }
        }
      }

    }
  }
 
  public <T> List<T> fetch(Query<T> query) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.NORMAL;
//    if(!pag.isPaginating()){
//      if(pag.pageSize==0)
//        pag.passivate();
//    }
    return (List<T>)doFetchList(query, Integer.MAX_VALUE, 0);
  }

  public <T> List<T> fetch(Query<T> query, int limit) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.NORMAL;
   
//    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
//    // use this limit only if not paginating
//    if(!pag.isPaginating()){
//      pag.activate();
//      pag.pageSize=limit;
//    }
    return (List<T>)doFetchList(query, limit, 0);
  }

  public <T> List<T> fetch(Query<T> query, int limit, Object offset) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.NORMAL;
//    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
//    QueryOptionOffset off = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
    // use this limit/offset only if not paginating
//    if(!pag.isPaginating()){
//      pag.activate();
//      pag.pageSize=limit;
//      off.activate();
//      off.offset = (Integer)offset;
//    }
    return (List<T>)doFetchList(query, limit, (Integer)offset);
  }

  public <T> int count(Query<T> query) {
    return prepare(query)
        .countEntities(FetchOptions.Builder.withDefaults());
  }

  public <T> int delete(Query<T> query) {
    final ArrayList<Key> keys = new ArrayList<Key>();

    Class<?> clazz = query.getQueriedClass();
    ClassInfo info = ClassInfo.getClassInfo(clazz);
       
    if(!info.hasAggregatedFields){
      for (final Entity entity : prepareKeysOnly(query).asIterable(
          FetchOptions.Builder.withDefaults())) {
        keys.add(entity.getKey());
      }
    }else {
      // if aggregated fields, needs to retrieve all objects to scan & delete aggregated children
      List<T> models = query.fetch();
      _deleteMultiple(models, keys, null, null, null);
    }

    ds.delete(keys);

    return keys.size();
  }

  public <T> List<T> fetchKeys(Query<T> query) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY;
//    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
//    if(!pag.isPaginating()){
//      pag.passivate();
//    }

    return (List<T>)doFetchList(query, Integer.MAX_VALUE, 0);
  }

  public <T> List<T> fetchKeys(Query<T> query, int limit) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY;
//    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
    // use this limit only if not paginating
//    if(!pag.isPaginating()){
//      pag.activate();
//      pag.pageSize=limit;
//    }

    return (List<T>)doFetchList(query, limit, 0);
  }

  public <T> List<T> fetchKeys(Query<T> query, int limit, Object offset) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY;
//    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
//    QueryOptionOffset off = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
    // use this limit/offset only if not paginating
//    if(!pag.isPaginating()){
//      pag.activate();
//      pag.pageSize=limit;
//      off.activate();
//      off.offset = (Integer)offset;
//    }

    return (List<T>)doFetchList(query, limit, (Integer)offset);
  }

  public <T> Iterable<T> iter(Query<T> query) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER;
//    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
//    if(!pag.isPaginating()){
//      pag.passivate();
//    }

    return doFetchIterable(query, Integer.MAX_VALUE, 0);
  }

  public <T> Iterable<T> iter(Query<T> query, int limit) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER;
//    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
//    // use this limit only if not paginating
//    if(!pag.isPaginating()){
//      pag.activate();
//      pag.pageSize=limit;
//    }

    return doFetchIterable(query, limit, 0);
  }

  public <T> Iterable<T> iter(Query<T> query, int limit, Object offset) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER;
//    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
//    QueryOptionOffset off = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
//    // use this limit/offset only if not paginating
//    if(!pag.isPaginating()){
//      pag.activate();
//      pag.pageSize=limit;
//      off.activate();
//      off.offset = (Integer)offset;
//    }

    return doFetchIterable(query, limit, (Integer)offset);
  }


  public <T> void release(Query<T> query) {
    super.release(query);
    GaeQueryUtils.release(query);
  }
 
  public <T> void paginate(Query<T> query) {
    GaeQueryUtils.paginate(query);
  }

  public <T> void nextPage(Query<T> query) {
    GaeQueryUtils.nextPage(query);
  }

  public <T> void previousPage(Query<T> query) {
    GaeQueryUtils.previousPage(query);
  }

  public int insert(Object... objects) {
    return _insertMultiple(Arrays.asList(objects));
  }

  public int insert(Iterable<?> objects) {
    return _insertMultiple(objects);
  }

  public int delete(Object... models) {
    return delete(Arrays.asList(models));
  }


  public int delete(Iterable<?> models) {
    List<Key> keys = new ArrayList<Key>();
    _deleteMultiple(models, keys, null, null, null);
   
    ds.delete(keys);
   
    return keys.size();
  }


  public <T> int deleteByKeys(Class<T> clazz, Object... keys) {
    return deleteByKeys(clazz, Arrays.asList(keys));
  }

  public <T> int deleteByKeys(Class<T> clazz, Iterable<?> keys) {
    List<Key> gaeKeys = new ArrayList<Key>();
    for(Object key:keys){
      gaeKeys.add(GaeMappingUtils.makeKeyFromId(clazz, key));
    }
   
    ds.delete(gaeKeys);

    return gaeKeys.size();
  }


  public int get(Object... objects) {
    return get(Arrays.asList(objects));
  }

  public <T> int get(Iterable<T> objects) {
    List<Key> keys = new ArrayList<Key>();
    for(Object obj:objects){
      keys.add(GaeMappingUtils.getKey(obj));
    }
   
    Map<Key, Entity> entityMap = ds.get(keys);
   
    for(Object obj:objects){
      Entity e = entityMap.get(GaeMappingUtils.getKey(obj));
      if(e!=null){
        GaeMappingUtils.fillModel(obj, e);
      }
    }
   
    return entityMap.size();
  }

  public <T> List<T> getByKeys(Class<T> clazz, Object... keys) {
    return getByKeys(clazz, Arrays.asList(keys));   
  }

  public <T> List<T> getByKeys(Class<T> clazz, Iterable<?> keys) {
    List<Key> gaeKeys = new ArrayList<Key>();
    ClassInfo info = ClassInfo.getClassInfo(clazz);
   
    for(Object key:keys){
      gaeKeys.add(GaeMappingUtils.makeKeyFromId(clazz, key));
    }
   
    Map<Key, Entity> entityMap = ds.get(gaeKeys);
    List<T> models = new ArrayList<T>(entityMap.size());
    for(Object key:keys){
      Entity entity = entityMap.get(GaeMappingUtils.makeKeyFromId(clazz, key));
      T obj = null;
      if(entity != null){
        obj = GaeMappingUtils.mapEntity(entity, clazz);
        if(obj != null){
          // related fields (Many<T> management mainly)
          if(!info.ownedFields.isEmpty()){
            mapOwned(obj);
          }
         
          // aggregated management
          if(!info.aggregatedFields.isEmpty()){
            mapAggregated(obj);
          }
         
          // join management
          if(!info.joinFields.isEmpty()){
            mapJoins(obj);
          }
         
        }
      }
      models.add(obj);
    }
   
    return models;
  }


  public <T> int update(Object... objects) {
    return update(Arrays.asList(objects));
  }

  public <T> int update(Iterable<T> objects) {
    return _updateMultiple(objects);
  }

  public <T> int update(Query<T> query, Map<String, ?> fieldValues) {
    throw new SienaException("update not implemented for GAE yet");
  }

 
  public int save(Object... objects) {
    return save(Arrays.asList(objects));
  }

  public int save(Iterable<?> objects) {
    List<Object> entities2Insert = new ArrayList<Object>();
    List<Object> entities2Update = new ArrayList<Object>();

    for(Object obj:objects){
      Class<?> clazz = obj.getClass();
      ClassInfo info = ClassInfo.getClassInfo(clazz);
      Field idField = info.getIdField();
     
      Object idVal = Util.readField(obj, idField);
      // id with null value means insert
      if(idVal == null){
        entities2Insert.add(obj);
      }
      // id with not null value means update
      else{
        entities2Update.add(obj);
      }
    }
    return insert(entities2Insert) + update(entities2Update);
  }
 
  private static String[] supportedOperators;

  static {
    supportedOperators = GaeQueryUtils.operators.keySet().toArray(new String[0]);
 

  public String[] supportedOperators() {
    return supportedOperators;
  }


}
TOP

Related Classes of siena.gae.GaePersistenceManager

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.