/* * @author mandubian <pascal.voitot@mandubian.org> */ package siena.gae; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.Future; import org.apache.commons.lang.NotImplementedException; import siena.ClassInfo; import siena.PersistenceManager; import siena.SienaException; import siena.Util; import siena.core.async.AbstractPersistenceManagerAsync; import siena.core.async.QueryAsync; import siena.core.async.SienaFuture; import siena.core.async.SienaFutureContainer; import siena.core.async.SienaFutureMock; import siena.core.async.SienaFutureWrapper; 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.AsyncDatastoreService; import com.google.appengine.api.datastore.Cursor; import com.google.appengine.api.datastore.DatastoreServiceFactory; import com.google.appengine.api.datastore.Entity; 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 GaePersistenceManagerAsync extends AbstractPersistenceManagerAsync { protected AsyncDatastoreService ds; protected PersistenceManager syncPm; /* * properties are not used but keeps it in case of... */ protected Properties props; public static final String DB = "GAE_ASYNC"; public void init(Properties p) { ds = DatastoreServiceFactory.getAsyncDatastoreService(); props = p; } public void init(Properties p, PersistenceManager syncPm) { this.syncPm = syncPm; ds = DatastoreServiceFactory.getAsyncDatastoreService(); props = p; } public PersistenceManager sync() { if(syncPm==null){ syncPm = new GaePersistenceManager(); syncPm.init(props); } return syncPm; } public SienaFuture<Void> insert(final Object obj) { final Class<?> clazz = obj.getClass(); final ClassInfo info = ClassInfo.getClassInfo(clazz); final Field idField = info.getIdField(); Entity entity = GaeMappingUtils.createEntityInstance(idField, info, obj); GaeMappingUtils.fillEntity(obj, entity); Future<Key> future = ds.put(entity); Future<Void> wrapped = new SienaFutureWrapper<Key, Void>(future) { @Override protected Void wrap(Key generatedKey) throws Exception { GaeMappingUtils.setIdFromKey(idField, obj, generatedKey); return null; } }; return new SienaFutureContainer<Void>(wrapped); } public SienaFuture<Void> delete(Object obj) { return new SienaFutureContainer<Void>(ds.delete(GaeMappingUtils.getKey(obj))); } public SienaFuture<Void> get(final Object obj) { final Key key = GaeMappingUtils.getKey(obj); Future<Entity> future = ds.get(key); Future<Void> wrapped = new SienaFutureWrapper<Entity, Void>(future) { @Override protected Void wrap(Entity entity) throws Exception { GaeMappingUtils.fillModel(obj, entity); return null; } }; return new SienaFutureContainer<Void>(wrapped); } public SienaFuture<Void> beginTransaction(int isolationLevel) { Future<Transaction> future = ds.beginTransaction(); Future<Void> wrapped = new SienaFutureWrapper<Transaction, Void>(future) { @Override protected Void wrap(Transaction transaction) throws Exception { return null; } }; return new SienaFutureContainer<Void>(wrapped); } public SienaFuture<Void> beginTransaction() { Future<Transaction> future = ds.beginTransaction(); Future<Void> wrapped = new SienaFutureWrapper<Transaction, Void>(future) { @Override protected Void wrap(Transaction transaction) throws Exception { return null; } }; return new SienaFutureContainer<Void>(wrapped); } public SienaFuture<Void> closeConnection() { // does nothing return null; } public SienaFuture<Void> commitTransaction() { Transaction txn = ds.getCurrentTransaction(); return new SienaFutureContainer<Void>(txn.commitAsync()); } public SienaFuture<Void> rollbackTransaction() { Transaction txn = ds.getCurrentTransaction(); return new SienaFutureContainer<Void>(txn.rollbackAsync()); } public SienaFuture<Void> update(Object obj) { Entity entity = new Entity(GaeMappingUtils.getKey(obj)); GaeMappingUtils.fillEntity(obj, entity); Future<Key> future = ds.put(entity); Future<Void> wrapped = new SienaFutureWrapper<Key, Void>(future) { @Override protected Void wrap(Key key) throws Exception { return null; } }; return new SienaFutureContainer<Void>(wrapped); } protected AsyncDatastoreService getDatastoreService() { return ds; } private <T> PreparedQuery prepare(QueryAsync<T> query) { Class<?> clazz = query.getQueriedClass(); com.google.appengine.api.datastore.Query q = new com.google.appengine.api.datastore.Query( ClassInfo.getClassInfo(clazz).tableName); return ds.prepare(GaeQueryUtils.addFiltersOrders(query, q)); } private <T> PreparedQuery prepareKeysOnly(QueryAsync<T> query) { Class<?> clazz = query.getQueriedClass(); com.google.appengine.api.datastore.Query q = new com.google.appengine.api.datastore.Query( ClassInfo.getClassInfo(clazz).tableName); return ds.prepare(GaeQueryUtils.addFiltersOrders(query, q).setKeysOnly()); } protected <T> T mapJoins(QueryAsync<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()){ Future<Map<Key, Entity>> entities = ds.get(fieldMap.get(field)); // gets the future here because we need it! entityMap.put(field, entities.get()); } }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(QueryAsync<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()){ Future<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.get()); } }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> T map(QueryAsync<T> query, Entity entity) { Class<?> clazz = query.getQueriedClass(); @SuppressWarnings("unchecked") T result = (T)GaeMappingUtils.mapEntity(entity, clazz); // join management if(!query.getJoins().isEmpty() || ClassInfo.getClassInfo(clazz).joinFields.size() != 0) return mapJoins(query, result); return result; } protected <T> List<T> map(QueryAsync<T> query, Iterable<Entity> entities) { Class<?> clazz = query.getQueriedClass(); @SuppressWarnings("unchecked") List<T> result = (List<T>) GaeMappingUtils.mapEntities(entities, clazz); // join management if(!query.getJoins().isEmpty() || ClassInfo.getClassInfo(clazz).joinFields.size() != 0) return mapJoins(query, result); return result; } protected <T> List<T> map(QueryAsync<T> query, QueryResultList<Entity> entities) { Class<?> clazz = query.getQueriedClass(); @SuppressWarnings("unchecked") List<T> result = (List<T>) GaeMappingUtils.mapEntities(entities, clazz); // join management if(!query.getJoins().isEmpty() || ClassInfo.getClassInfo(clazz).joinFields.size() != 0) return mapJoins(query, result); return result; } private <T> SienaFuture<List<T>> doFetchList(QueryAsync<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 SienaFutureMock<List<T>>(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 Iterable<Entity> entities = prepareKeysOnly(query).asIterable(fetchOptions); return new GaeSienaFutureListMapper<T>(this, entities, query, GaeSienaFutureListMapper.MapType.KEYS_ONLY); } case NORMAL: 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 GaeSienaFutureListMapper<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 by using offset 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(offset.offset); //} 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()); } }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 new GaeSienaFutureListMapper<T>( this, entities, query, GaeSienaFutureListMapper.MapType.KEYS_ONLY); } case NORMAL: default: { PreparedQuery pq = prepare(query); if(!gaeCtx.useCursor){ // then uses offset (in case of IN or != operators) //if(offset.isActive()){ // fetchOptions.offset(offset.offset); //} 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(gaeCtx.useCursor){ if(pag.isPaginating()){ Cursor cursor = entities.getCursor(); if(cursor!=null){ gaeCtx.addCursor(cursor.toWebSafeString()); } }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 new GaeSienaFutureListMapper<T>(this, entities, query); } } }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(offset.offset); //} 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(gaeCtx.useCursor){ if(pag.isPaginating()){ Cursor cursor = entities.getCursor(); if(cursor!=null){ gaeCtx.addCursor(cursor.toWebSafeString()); } }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 new GaeSienaFutureListMapper<T>( this, entities, query, GaeSienaFutureListMapper.MapType.KEYS_ONLY); } 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(offset.offset); //} 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(gaeCtx.useCursor){ if(pag.isPaginating()){ Cursor cursor = entities.getCursor(); if(cursor!=null){ gaeCtx.addCursor(cursor.toWebSafeString()); } }else{ Cursor cursor = entities.getCursor(); if(cursor!=null){ gaeCtx.addAndMoveCursor(entities.getCursor().toWebSafeString()); } // keeps track of the offset anyway gaeCtx.realOffset+=entities.size(); } //} return new GaeSienaFutureListMapper<T>(this, entities, query); } } } } } private <T> SienaFuture<Iterable<T>> doFetchIterable(QueryAsync<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 SienaFutureMock<Iterable<T>>(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 GaeSienaFutureIterableMapper<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 GaeSienaFutureIterableMapper<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 GaeSienaFutureIterableMapperWithCursor<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(offset.offset); //} 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 GaeSienaFutureIterableMapper<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(offset.offset); //} 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 GaeSienaFutureIterableMapperWithCursor<T>(this, entities, query); } } } } } } public <T> SienaFuture<Integer> count(QueryAsync<T> query) { int nb = prepare(query) .countEntities(FetchOptions.Builder.withDefaults()); return new SienaFutureMock<Integer>(nb); } public <T> SienaFuture<Integer> delete(QueryAsync<T> query) { final ArrayList<Key> keys = new ArrayList<Key>(); for (final Entity entity : prepareKeysOnly(query).asIterable( FetchOptions.Builder.withDefaults())) { keys.add(entity.getKey()); } Future<Void> future = ds.delete(keys); Future<Integer> wrapped = new SienaFutureWrapper<Void, Integer>(future) { @Override protected Integer wrap(Void v) throws Exception { return keys.size(); } }; return new SienaFutureContainer<Integer>(wrapped); } public <T> SienaFuture<List<T>> fetch(QueryAsync<T> query) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.NORMAL; return doFetchList(query, Integer.MAX_VALUE, 0); } public <T> SienaFuture<List<T>> fetch(QueryAsync<T> query, int limit) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.NORMAL; return doFetchList(query, limit, 0); } public <T> SienaFuture<List<T>> fetch(QueryAsync<T> query, int limit, Object offset) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.NORMAL; return doFetchList(query, limit, (Integer)offset); } public <T> SienaFuture<List<T>> fetchKeys(QueryAsync<T> query) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY; return doFetchList(query, Integer.MAX_VALUE, 0); } public <T> SienaFuture<List<T>> fetchKeys(QueryAsync<T> query, int limit) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY; return doFetchList(query, limit, 0); } public <T> SienaFuture<List<T>> fetchKeys(QueryAsync<T> query, int limit, Object offset) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY; return doFetchList(query, limit, (Integer)offset); } public <T> SienaFuture<Iterable<T>> iter(QueryAsync<T> query) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER; return doFetchIterable(query, Integer.MAX_VALUE, 0); } public <T> SienaFuture<Iterable<T>> iter(QueryAsync<T> query, int limit) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER; return doFetchIterable(query, limit, 0); } public <T> SienaFuture<Iterable<T>> iter(QueryAsync<T> query, int limit, Object offset) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER; return doFetchIterable(query, limit, (Integer)offset); } public <T> SienaFuture<Iterable<T>> iterPerPage(QueryAsync<T> query, int pageSize) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER_PER_PAGE; return new SienaIterableAsyncPerPageWrapper<T>(query, pageSize); } public <T> void release(QueryAsync<T> query) { super.release(query); GaeQueryUtils.release(query); } public <T> void paginate(QueryAsync<T> query) { GaeQueryUtils.paginate(query); } public <T> void nextPage(QueryAsync<T> query) { GaeQueryUtils.nextPage(query); } public <T> void previousPage(QueryAsync<T> query) { GaeQueryUtils.previousPage(query); } public SienaFuture<Integer> insert(final Object... objects) { List<Entity> entities = new ArrayList<Entity>(objects.length); for(int i=0; i<objects.length;i++){ Class<?> clazz = objects[i].getClass(); ClassInfo info = ClassInfo.getClassInfo(clazz); Field idField = info.getIdField(); Entity entity = GaeMappingUtils.createEntityInstance(idField, info, objects[i]); GaeMappingUtils.fillEntity(objects[i], entity); entities.add(entity); } Future<List<Key>> future = ds.put(entities); Future<Integer> wrapped = new SienaFutureWrapper<List<Key>, Integer>(future) { @Override protected Integer wrap(List<Key> generatedKeys) throws Exception { int i=0; for(Object obj:objects){ Class<?> clazz = obj.getClass(); ClassInfo info = ClassInfo.getClassInfo(clazz); Field idField = info.getIdField(); GaeMappingUtils.setIdFromKey(idField, obj, generatedKeys.get(i++)); } return generatedKeys.size(); } }; return new SienaFutureContainer<Integer>(wrapped); } public SienaFuture<Integer> insert(final Iterable<?> objects) { List<Entity> entities = new ArrayList<Entity>(); for(Object obj:objects){ Class<?> clazz = obj.getClass(); ClassInfo info = ClassInfo.getClassInfo(clazz); Field idField = info.getIdField(); Entity entity = GaeMappingUtils.createEntityInstance(idField, info, obj); GaeMappingUtils.fillEntity(obj, entity); entities.add(entity); } Future<List<Key>> future = ds.put(entities); Future<Integer> wrapped = new SienaFutureWrapper<List<Key>, Integer>(future) { @Override protected Integer wrap(List<Key> generatedKeys) throws Exception { int i=0; for(Object obj:objects){ Class<?> clazz = obj.getClass(); ClassInfo info = ClassInfo.getClassInfo(clazz); Field idField = info.getIdField(); GaeMappingUtils.setIdFromKey(idField, obj, generatedKeys.get(i++)); } return generatedKeys.size(); } }; return new SienaFutureContainer<Integer>(wrapped); } public SienaFuture<Integer> delete(final Object... models) { final List<Key> keys = new ArrayList<Key>(); for(Object obj:models){ keys.add(GaeMappingUtils.getKey(obj)); } Future<Void> future = ds.delete(keys); Future<Integer> wrapped = new SienaFutureWrapper<Void, Integer>(future) { @Override protected Integer wrap(Void v) throws Exception { return keys.size(); } }; return new SienaFutureContainer<Integer>(wrapped); } public SienaFuture<Integer> delete(final Iterable<?> models) { final List<Key> keys = new ArrayList<Key>(); for(Object obj:models){ keys.add(GaeMappingUtils.getKey(obj)); } Future<Void> future = ds.delete(keys); Future<Integer> wrapped = new SienaFutureWrapper<Void, Integer>(future) { @Override protected Integer wrap(Void v) throws Exception { return keys.size(); } }; return new SienaFutureContainer<Integer>(wrapped); } public <T> SienaFuture<Integer> deleteByKeys(Class<T> clazz, Object... keys) { final List<Key> gaeKeys = new ArrayList<Key>(); for(Object key:keys){ gaeKeys.add(GaeMappingUtils.makeKeyFromId(clazz, key)); } Future<Void> future = ds.delete(gaeKeys); Future<Integer> wrapped = new SienaFutureWrapper<Void, Integer>(future) { @Override protected Integer wrap(Void v) throws Exception { return gaeKeys.size(); } }; return new SienaFutureContainer<Integer>(wrapped); } public <T> SienaFuture<Integer> deleteByKeys(Class<T> clazz, Iterable<?> keys) { final List<Key> gaeKeys = new ArrayList<Key>(); for(Object key:keys){ gaeKeys.add(GaeMappingUtils.makeKeyFromId(clazz, key)); } Future<Void> future = ds.delete(gaeKeys); Future<Integer> wrapped = new SienaFutureWrapper<Void, Integer>(future) { @Override protected Integer wrap(Void v) throws Exception { return gaeKeys.size(); } }; return new SienaFutureContainer<Integer>(wrapped); } public SienaFuture<Integer> get(final Object... objects) { List<Key> keys = new ArrayList<Key>(); for(Object obj:objects){ keys.add(GaeMappingUtils.getKey(obj)); } Future<Map<Key, Entity>> future = ds.get(keys); Future<Integer> wrapped = new SienaFutureWrapper<Map<Key, Entity>, Integer>(future) { @Override protected Integer wrap(Map<Key, Entity> entityMap) throws Exception { for(Object obj:objects){ GaeMappingUtils.fillModel(obj, entityMap.get(GaeMappingUtils.getKey(obj))); } return entityMap.size(); } }; return new SienaFutureContainer<Integer>(wrapped); } public <T> SienaFuture<Integer> get(final Iterable<T> objects) { List<Key> keys = new ArrayList<Key>(); for(Object obj:objects){ keys.add(GaeMappingUtils.getKey(obj)); } Future<Map<Key, Entity>> future = ds.get(keys); Future<Integer> wrapped = new SienaFutureWrapper<Map<Key, Entity>, Integer>(future) { @Override protected Integer wrap(Map<Key, Entity> entityMap) throws Exception { for(Object obj:objects){ GaeMappingUtils.fillModel(obj, entityMap.get(GaeMappingUtils.getKey(obj))); } return entityMap.size(); } }; return new SienaFutureContainer<Integer>(wrapped); } public <T> SienaFuture<T> getByKey(final Class<T> clazz, final Object key) { Key gkey = GaeMappingUtils.makeKeyFromId(clazz, key); try { Future<Entity> future = ds.get(gkey); Future<T> wrapped = new SienaFutureWrapper<Entity, T>(future) { @Override protected T wrap(Entity entity) throws Exception { T obj = Util.createObjectInstance(clazz); GaeMappingUtils.fillModelAndKey(obj, entity); return obj; } }; return new SienaFutureContainer<T>(wrapped); } catch (Exception e) { throw new SienaException(e); } } public <T> SienaFuture<List<T>> getByKeys(final Class<T> clazz, final Object... keys) { List<Key> gaeKeys = new ArrayList<Key>(); for(Object key:keys){ gaeKeys.add(GaeMappingUtils.makeKeyFromId(clazz, key)); } Future<Map<Key, Entity>> future = ds.get(gaeKeys); Future<List<T>> wrapped = new SienaFutureWrapper<Map<Key, Entity>, List<T>>(future) { @Override protected List<T> wrap(Map<Key, Entity> entityMap) throws Exception { List<T> models = new ArrayList<T>(entityMap.size()); for(Object key:keys){ models.add(GaeMappingUtils.mapEntity(entityMap.get(GaeMappingUtils.makeKeyFromId(clazz, key)), clazz)); } return models; } }; return new SienaFutureContainer<List<T>>(wrapped); } public <T> SienaFuture<List<T>> getByKeys(final Class<T> clazz, final Iterable<?> keys) { List<Key> gaeKeys = new ArrayList<Key>(); for(Object key:keys){ gaeKeys.add(GaeMappingUtils.makeKeyFromId(clazz, key)); } Future<Map<Key, Entity>> future = ds.get(gaeKeys); Future<List<T>> wrapped = new SienaFutureWrapper<Map<Key, Entity>, List<T>>(future) { @Override protected List<T> wrap(Map<Key, Entity> entityMap) throws Exception { List<T> models = new ArrayList<T>(entityMap.size()); for(Object key:keys){ models.add(GaeMappingUtils.mapEntity(entityMap.get(GaeMappingUtils.makeKeyFromId(clazz, key)), clazz)); } return models; } }; return new SienaFutureContainer<List<T>>(wrapped); } @Override public SienaFuture<Integer> update(Object... objects) { //throw new NotImplementedException("update not implemented for GAE yet"); List<Entity> entities = new ArrayList<Entity>(); for(Object obj:objects){ Class<?> clazz = obj.getClass(); ClassInfo info = ClassInfo.getClassInfo(clazz); Entity entity = GaeMappingUtils.createEntityInstanceForUpdate(info, obj); GaeMappingUtils.fillEntity(obj, entity); entities.add(entity); } Future<List<Key>> future = ds.put(entities); Future<Integer> wrapped = new SienaFutureWrapper<List<Key>, Integer>(future) { @Override protected Integer wrap(List<Key> keys) throws Exception { return keys.size(); } }; return new SienaFutureContainer<Integer>(wrapped); } @Override public <T> SienaFuture<Integer> update(Iterable<T> objects) { //throw new NotImplementedException("update not implemented for GAE yet"); List<Entity> entities = new ArrayList<Entity>(); for(Object obj:objects){ Class<?> clazz = obj.getClass(); ClassInfo info = ClassInfo.getClassInfo(clazz); Entity entity = GaeMappingUtils.createEntityInstanceForUpdate(info, obj); GaeMappingUtils.fillEntity(obj, entity); entities.add(entity); } Future<List<Key>> future = ds.put(entities); Future<Integer> wrapped = new SienaFutureWrapper<List<Key>, Integer>(future) { @Override protected Integer wrap(List<Key> keys) throws Exception { return keys.size(); } }; return new SienaFutureContainer<Integer>(wrapped); } @Override public <T> SienaFuture<Integer> update(QueryAsync<T> query, Map<String, ?> fieldValues) { throw new NotImplementedException("update not implemented for GAE yet"); } public SienaFuture<Void> save(final Object obj) { Class<?> clazz = obj.getClass(); ClassInfo info = ClassInfo.getClassInfo(clazz); final Field idField = info.getIdField(); final Entity entity; final Object idVal = Util.readField(obj, idField); // id with null value means insert if(idVal == null){ entity = GaeMappingUtils.createEntityInstance(idField, info, obj); } // id with not null value means update else{ entity = GaeMappingUtils.createEntityInstanceForUpdate(info, obj); } GaeMappingUtils.fillEntity(obj, entity); Future<Key> future = ds.put(entity); Future<Void> wrapped = new SienaFutureWrapper<Key, Void>(future) { @Override protected Void wrap(Key generatedKey) throws Exception { if(idVal == null){ GaeMappingUtils.setIdFromKey(idField, obj, entity.getKey()); } return null; } }; return new SienaFutureContainer<Void>(wrapped); } public SienaFuture<Integer> save(final Object... objects) { List<Entity> entities = new ArrayList<Entity>(); for(Object obj:objects){ 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){ entity = GaeMappingUtils.createEntityInstance(idField, info, obj); } // id with not null value means update else{ entity = GaeMappingUtils.createEntityInstanceForUpdate(info, obj); } GaeMappingUtils.fillEntity(obj, entity); entities.add(entity); } Future<List<Key>> future = ds.put(entities); Future<Integer> wrapped = new SienaFutureWrapper<List<Key>, Integer>(future) { @Override protected Integer wrap(List<Key> keys) throws Exception { int i=0; for(Object obj:objects){ Class<?> clazz = obj.getClass(); ClassInfo info = ClassInfo.getClassInfo(clazz); Field idField = info.getIdField(); Object idVal = Util.readField(obj, idField); if(idVal == null){ GaeMappingUtils.setIdFromKey(idField, obj, keys.get(i++)); } } return keys.size(); } }; return new SienaFutureContainer<Integer>(wrapped); } public SienaFuture<Integer> save(final Iterable<?> objects) { List<Entity> entities = new ArrayList<Entity>(); for(Object obj:objects){ 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){ entity = GaeMappingUtils.createEntityInstance(idField, info, obj); } // id with not null value means update else{ entity = GaeMappingUtils.createEntityInstanceForUpdate(info, obj); } GaeMappingUtils.fillEntity(obj, entity); entities.add(entity); } Future<List<Key>> future = ds.put(entities); Future<Integer> wrapped = new SienaFutureWrapper<List<Key>, Integer>(future) { @Override protected Integer wrap(List<Key> keys) throws Exception { int i=0; for(Object obj:objects){ Class<?> clazz = obj.getClass(); ClassInfo info = ClassInfo.getClassInfo(clazz); Field idField = info.getIdField(); Object idVal = Util.readField(obj, idField); if(idVal == null){ GaeMappingUtils.setIdFromKey(idField, obj, keys.get(i++)); } } return keys.size(); } }; return new SienaFutureContainer<Integer>(wrapped); } private static String[] supportedOperators; static { supportedOperators = GaeQueryUtils.operators.keySet().toArray(new String[0]); } @Override public String[] supportedOperators() { return supportedOperators; } }