/*
* 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;
}
}