/* * 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. */ package siena.sdb; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import siena.AbstractPersistenceManager; import siena.ClassInfo; import siena.Query; import siena.QueryJoin; import siena.SienaException; import siena.Util; 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 siena.gae.GaeMappingUtils; import siena.gae.GaeQueryUtils; import siena.gae.QueryOptionGaeContext; import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.simpledb.AmazonSimpleDB; import com.amazonaws.services.simpledb.AmazonSimpleDBClient; import com.amazonaws.services.simpledb.model.BatchDeleteAttributesRequest; import com.amazonaws.services.simpledb.model.BatchPutAttributesRequest; import com.amazonaws.services.simpledb.model.CreateDomainRequest; import com.amazonaws.services.simpledb.model.DeletableItem; import com.amazonaws.services.simpledb.model.GetAttributesRequest; import com.amazonaws.services.simpledb.model.GetAttributesResult; import com.amazonaws.services.simpledb.model.ReplaceableItem; import com.amazonaws.services.simpledb.model.SelectRequest; import com.amazonaws.services.simpledb.model.SelectResult; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.Key; public class SdbPersistenceManager extends AbstractPersistenceManager { public static final String DB = "SDB"; private static final String[] supportedOperators = { "<", ">", ">=", "<=", "!=", "=", "LIKE", "NOT LIKE", "IN" }; public final static PmOptionSdbReadConsistency CONSISTENT_READ = new PmOptionSdbReadConsistency(true); public final static PmOptionSdbReadConsistency NOT_CONSISTENT_READ = new PmOptionSdbReadConsistency(false); public final static int MAX_ITEMS_PER_CALL = 25; public final static int MAX_ATTR_PER_SELECT = 20; private AmazonSimpleDB sdb; private String prefix; private List<String> domains; public void init(Properties p) { String awsAccessKeyId = p.getProperty("awsAccessKeyId"); String awsSecretAccessKey = p.getProperty("awsSecretAccessKey"); if(awsAccessKeyId == null || awsSecretAccessKey == null) throw new SienaException("Both awsAccessKeyId and awsSecretAccessKey properties must be set"); prefix = p.getProperty("prefix"); if(prefix == null) prefix = ""; sdb = new AmazonSimpleDBClient(new BasicAWSCredentials(awsAccessKeyId, awsSecretAccessKey)); } public void checkDomain(String domainName) { if(domains == null) { domains = sdb.listDomains().getDomainNames(); } if(!domains.contains(domainName)) { sdb.createDomain(new CreateDomainRequest(domainName)); domains.add(domainName); } } public boolean isReadConsistent() { PmOptionSdbReadConsistency opt = (PmOptionSdbReadConsistency)option(CONSISTENT_READ.type); if(opt != null) { return opt.isConsistentRead; } return false; } public void insert(Object obj) { Class<?> clazz = obj.getClass(); ClassInfo info = ClassInfo.getClassInfo(clazz); String domain = SdbMappingUtils.getDomainName(clazz, prefix); try { checkDomain(domain); sdb.putAttributes(SdbMappingUtils.createPutRequest(domain, clazz, info, obj)); }catch(AmazonClientException ex){ throw new SienaException(ex); } } @Override public int insert(Object... objects) { return insert(Arrays.asList(objects)); } @Override public int insert(Iterable<?> objects) { Map<String, List<ReplaceableItem>> doMap = new HashMap<String, List<ReplaceableItem>>(); int nb = 0; for(Object obj: objects){ Class<?> clazz = obj.getClass(); String domain = SdbMappingUtils.getDomainName(clazz, prefix); List<ReplaceableItem> doList = doMap.get(domain); if(doList == null){ doList = new ArrayList<ReplaceableItem>(); doMap.put(domain, doList); } doList.add(SdbMappingUtils.createItem(obj)); nb++; } try { for(String domain: doMap.keySet()){ checkDomain(domain); List<ReplaceableItem> doList = doMap.get(domain); int len = doList.size()> MAX_ITEMS_PER_CALL ? MAX_ITEMS_PER_CALL: doList.size(); for(int i=0; i < doList.size(); i += len){ int sz = i+len; if(sz > doList.size()){ sz = doList.size(); } sdb.batchPutAttributes( new BatchPutAttributesRequest( domain, doList.subList(i, sz))); } } }catch(AmazonClientException ex){ throw new SienaException(ex); } return nb; } public void get(Object obj) { Class<?> clazz = obj.getClass(); ClassInfo info = ClassInfo.getClassInfo(obj.getClass()); String domain = SdbMappingUtils.getDomainName(clazz, prefix); try { checkDomain(domain); GetAttributesRequest req = SdbMappingUtils.createGetRequest(domain, clazz, obj); // sets consistent read to true when reading one single object req.setConsistentRead(isReadConsistent()); GetAttributesResult res = sdb.getAttributes(req); if(res.getAttributes().size() == 0){ throw new SienaException(req.getItemName()+" not found in domain "+req.getDomainName()); } SdbMappingUtils.fillModel(req.getItemName(), res, clazz, obj); // join management if(!info.joinFields.isEmpty()){ mapJoins(obj); } }catch(AmazonClientException ex){ throw new SienaException(ex); } } public int get(Object... models) { return get(Arrays.asList(models)); } protected <T> int rawGet(Iterable<T> models) { StringBuffer domainBuf = new StringBuffer(); SelectRequest req = SdbMappingUtils.buildBatchGetQuery(models, prefix, domainBuf); req.setConsistentRead(isReadConsistent()); try { checkDomain(domainBuf.toString()); SelectResult res = sdb.select(req); int nb = SdbMappingUtils.mapSelectResult(res, models); // join management // gets class Class<?> clazz = null; for(T obj: models){ if(clazz == null){ clazz = obj.getClass(); break; } } if(!ClassInfo.getClassInfo(clazz).joinFields.isEmpty()){ mapJoins(models); } return nb; }catch(AmazonClientException ex){ throw new SienaException(ex); } } public <T> int get(Iterable<T> models) { Iterator<T> it = models.iterator(); int total = 0; while(it.hasNext()){ List<T> subList = new ArrayList<T>(); for(int i=0; i < MAX_ATTR_PER_SELECT && it.hasNext();i++){ subList.add(it.next()); } if(!subList.isEmpty()){ total += rawGet(subList); } } return total; } public void update(Object obj) { Class<?> clazz = obj.getClass(); ClassInfo info = ClassInfo.getClassInfo(clazz); String domain = SdbMappingUtils.getDomainName(clazz, prefix); try { checkDomain(domain); sdb.putAttributes(SdbMappingUtils.createPutRequest(domain, clazz, info, obj)); }catch(AmazonClientException ex){ throw new SienaException(ex); } } public <T> int update(Object... models) { return update(Arrays.asList(models)); } public <T> int update(Iterable<T> models) { Map<String, List<ReplaceableItem>> doMap = new HashMap<String, List<ReplaceableItem>>(); int nb = 0; for(Object obj: models){ Class<?> clazz = obj.getClass(); String domain = SdbMappingUtils.getDomainName(clazz, prefix); List<ReplaceableItem> doList = doMap.get(domain); if(doList == null){ doList = new ArrayList<ReplaceableItem>(); doMap.put(domain, doList); } doList.add(SdbMappingUtils.createItem(obj)); nb++; } try { for(String domain: doMap.keySet()){ checkDomain(domain); List<ReplaceableItem> doList = doMap.get(domain); int len = doList.size()> MAX_ITEMS_PER_CALL ? MAX_ITEMS_PER_CALL: doList.size(); for(int i=0; i < doList.size(); i += len){ int sz = i+len; if(sz > doList.size()){ sz = doList.size(); } sdb.batchPutAttributes( new BatchPutAttributesRequest( domain, doList.subList(i, sz))); } } }catch(AmazonClientException ex){ throw new SienaException(ex); } return nb; } public void delete(Object obj) { Class<?> clazz = obj.getClass(); String domain = SdbMappingUtils.getDomainName(clazz, prefix); try { checkDomain(domain); sdb.deleteAttributes(SdbMappingUtils.createDeleteRequest(domain, clazz, obj)); }catch(AmazonClientException ex){ throw new SienaException(ex); } } public int delete(Object... models) { return delete(Arrays.asList(models)); } @Override public int delete(Iterable<?> models) { Map<String, List<DeletableItem>> doMap = new HashMap<String, List<DeletableItem>>(); int nb = 0; for(Object obj: models){ Class<?> clazz = obj.getClass(); String domain = SdbMappingUtils.getDomainName(clazz, prefix); List<DeletableItem> doList = doMap.get(domain); if(doList == null){ doList = new ArrayList<DeletableItem>(); doMap.put(domain, doList); } doList.add(SdbMappingUtils.createDeletableItem(obj)); nb++; } try { for(String domain: doMap.keySet()){ checkDomain(domain); List<DeletableItem> doList = doMap.get(domain); int len = doList.size() > MAX_ITEMS_PER_CALL ? MAX_ITEMS_PER_CALL: doList.size(); for(int i=0; i < doList.size(); i += len){ int sz = i+len; if(sz > doList.size()){ sz = doList.size(); } sdb.batchDeleteAttributes( new BatchDeleteAttributesRequest( domain, doList.subList(i, sz))); } } }catch(AmazonClientException ex){ throw new SienaException(ex); } return nb; } @Override 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); } } @Override public int save(Object... objects) { return save(Arrays.asList(objects)); } @Override 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); } public <T> T getByKey(Class<T> clazz, Object key) { String domain = SdbMappingUtils.getDomainName(clazz, prefix); try { checkDomain(domain); GetAttributesRequest req = SdbMappingUtils.createGetRequestFromKey(domain, clazz, key); // sets consistent read to true when reading one single object req.setConsistentRead(isReadConsistent()); GetAttributesResult res = sdb.getAttributes(req); if(res.getAttributes().size() == 0){ throw new SienaException(req.getItemName()+" not found in domain "+req.getDomainName()); } T obj = Util.createObjectInstance(clazz); SdbMappingUtils.fillModel(req.getItemName(), res, clazz, obj); // join management if(!ClassInfo.getClassInfo(clazz).joinFields.isEmpty()){ mapJoins(obj); } return obj; }catch(AmazonClientException ex){ throw new SienaException(ex); } } public <T> List<T> getByKeys(Class<T> clazz, Object... keys) { return getByKeys(clazz, Arrays.asList(keys)); } protected <T> List<T> rawGetByKeys(Class<T> clazz, Iterable<?> keys) { try { StringBuffer domainBuf = new StringBuffer(); SelectRequest req = SdbMappingUtils.buildBatchGetQueryByKeys(clazz, keys, prefix, domainBuf); checkDomain(domainBuf.toString()); req.setConsistentRead(isReadConsistent()); SelectResult res = sdb.select(req); List<T> models = new ArrayList<T>(); SdbMappingUtils.mapSelectResultToListOrderedFromKeys(res, models, clazz, keys); // join management if(!ClassInfo.getClassInfo(clazz).joinFields.isEmpty()){ mapJoins(models); } return models; }catch(AmazonClientException ex){ throw new SienaException(ex); } } public <T> List<T> getByKeys(Class<T> clazz, Iterable<?> keys) { Iterator<?> it = keys.iterator(); List<T> list = new ArrayList<T>(); while(it.hasNext()){ List<Object> subList = new ArrayList<Object>(); for(int i=0; i < MAX_ATTR_PER_SELECT && it.hasNext();i++){ subList.add(it.next()); } if(!subList.isEmpty()){ list.addAll(rawGetByKeys(clazz, subList)); } } return list; } public <T> int count(Query<T> query) { StringBuffer domainBuf = new StringBuffer(); SelectRequest req = SdbMappingUtils.buildCountQuery(query, prefix, domainBuf); try { checkDomain(domainBuf.toString()); req.setConsistentRead(isReadConsistent()); SelectResult res = sdb.select(req); return SdbMappingUtils.mapSelectResultToCount(res); }catch(AmazonClientException ex){ throw new SienaException(ex); } } public <T> List<T> fetch(Query<T> query) { List<T> models = new ArrayList<T>(); doFetchList(query, Integer.MAX_VALUE, 0, models, 0); return models; } public <T> List<T> fetch(Query<T> query, int limit) { List<T> models = new ArrayList<T>(); doFetchList(query, limit, 0, models, 0); return models; } public <T> List<T> fetch(Query<T> query, int limit, Object offset) { List<T> models = new ArrayList<T>(); doFetchList(query, limit, (Integer)offset, models, 0); return models; } public <T> List<T> fetchKeys(Query<T> query) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY; List<T> models = new ArrayList<T>(); doFetchList(query, Integer.MAX_VALUE, 0, models, 0); return models; } public <T> List<T> fetchKeys(Query<T> query, int limit) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY; List<T> models = new ArrayList<T>(); doFetchList(query, limit, 0, models, 0); return models; } public <T> List<T> fetchKeys(Query<T> query, int limit, Object offset) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY; List<T> models = new ArrayList<T>(); doFetchList(query, limit, (Integer)offset, models, 0); return models; } public <T> Iterable<T> iter(Query<T> query) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER; return doFetchIterable(query, Integer.MAX_VALUE, 0, false); } public <T> Iterable<T> iter(Query<T> query, int limit) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER; return doFetchIterable(query, limit, 0, false); } public <T> Iterable<T> iter(Query<T> query, int limit, Object offset) { ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER; return doFetchIterable(query, limit, (Integer)offset, false); } public <T> int delete(Query<T> query) { List<T> l = fetchKeys(query); return delete(l); } protected <T> void postMapping(Query<T> query){ QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID); QueryOptionSdbContext sdbCtx = (QueryOptionSdbContext)query.option(QueryOptionSdbContext.ID); QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID); QueryOptionOffset off = (QueryOptionOffset)query.option(QueryOptionOffset.ID); // desactivates paging & offset in stateless mode if(state.isStateless() && !pag.isPaginating()){ pag.passivate(); pag.pageSize = 0; sdbCtx.resetAll(); } // offset if not kept as it is never reused as is even in stateful // mode. the stateful mode only keeps the realOffset/pagination alive off.passivate(); off.offset = 0; } protected <T> void continueFetchNextToken(Query<T> query, List<T> results, int depth){ QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID); QueryOptionSdbContext sdbCtx = (QueryOptionSdbContext)query.option(QueryOptionSdbContext.ID); QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID); QueryOptionOffset off = (QueryOptionOffset)query.option(QueryOptionOffset.ID); // desactivates offset not to use if fetching more items from next token if(state.isStateless()){ off.passivate(); } if(!pag.isActive()){ if(state.isStateless()){ // retrieves next token if(sdbCtx.nextToken()!=null){ doFetchList(query, Integer.MAX_VALUE, 0, results, depth+1); } }else { if(sdbCtx.currentToken()!=null){ // desactivates offset because we don't to go on using offset while going to next tokens boolean b = off.isActive(); off.passivate(); doFetchList(query, Integer.MAX_VALUE, 0, results, depth+1); // reactivate it if it was activated if(b) off.activate(); } } } } protected <T> void postFetch(Query<T> query, SelectResult res) { QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID); QueryOptionSdbContext sdbCtx = (QueryOptionSdbContext)query.option(QueryOptionSdbContext.ID); QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID); QueryOptionOffset off = (QueryOptionOffset)query.option(QueryOptionOffset.ID); if(sdbCtx.realPageSize == 0){ sdbCtx.realPageSize = res.getItems().size(); } String token = null; // sets the current cursor (in stateful mode, cursor is always kept for further use) if(pag.isPaginating()){ token = res.getNextToken(); if(token!=null){ sdbCtx.addToken(token, sdbCtx.realOffset + sdbCtx.realPageSize + off.offset); } // if paginating and 0 results then no more data else resets noMoreDataAfter if(res.getItems().size()==0){ sdbCtx.noMoreDataAfter = true; } else { sdbCtx.noMoreDataAfter = false; } }else{ if(state.isStateless()){ // in stateless, doesn't follow real offset & stays where it is sdbCtx.realOffset = off.offset; token = res.getNextToken(); if(token!=null){ sdbCtx.addToken(token, /*sdbCtx.realOffset +*/ sdbCtx.realPageSize); } }else { // follows the real offset in stateful mode sdbCtx.realOffset += sdbCtx.realPageSize /*+ off.offset*/; token = res.getNextToken(); if(token!=null){ sdbCtx.addAndMoveToken(token, sdbCtx.realOffset); }else { // forces to go to next token to invalidate current one // if there are no token after, currentToken will be null sdbCtx.nextToken(); } } } } protected <T> void preFetch(Query<T> query, int limit, int offset, boolean recursing){ QueryOptionSdbContext sdbCtx = (QueryOptionSdbContext)query.option(QueryOptionSdbContext.ID); QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID); QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID); QueryOptionOffset off = (QueryOptionOffset)query.option(QueryOptionOffset.ID); if(sdbCtx==null){ sdbCtx = new QueryOptionSdbContext(); query.customize(sdbCtx); } if(!pag.isPaginating()){ if(state.isStateless()){ // if not empty, it means we are recursing on tokens sdbCtx.reset(recursing); } // no pagination but pageOption active if(pag.isActive()){ // if local limit is set, it overrides the pageOption.pageSize if(limit!=Integer.MAX_VALUE){ sdbCtx.realPageSize = limit; // DONT DO THAT BECAUSE IT PREVENTS GOING TO NEXT TOKENS USING PAGE SIZE // pageOption is passivated to be sure it is not reused //pag.passivate(); } // using pageOption.pageSize else { sdbCtx.realPageSize = pag.pageSize; // DONT DO THAT BECAUSE IT PREVENTS GOING TO NEXT TOKENS USING PAGE SIZE // passivates the pageOption in stateless mode not to keep anything between 2 requests //if(state.isStateless()){ // pag.passivate(); //} } } else { if(limit != Integer.MAX_VALUE){ sdbCtx.realPageSize = limit; // activates paging (but not pagination) pag.activate(); }else { sdbCtx.realPageSize = 0; } } }else { // paginating so use the pagesize and don't passivate pageOption // local limit is not taken into account sdbCtx.realPageSize = pag.pageSize; } // if local offset has been set, uses it if(offset!=0){ off.activate(); off.offset = offset; } } protected final int MAX_DEPTH = 25; protected <T> void doFetchList(Query<T> query, int limit, int offset, List<T> resList, int depth) { if(depth >= MAX_DEPTH){ throw new SienaException("Reached maximum depth of recursion when retrieving more data ("+MAX_DEPTH+")"); } preFetch(query, limit, offset, !resList.isEmpty()); QueryOptionSdbContext sdbCtx = (QueryOptionSdbContext)query.option(QueryOptionSdbContext.ID); QueryOptionFetchType fetchType = (QueryOptionFetchType)query.option(QueryOptionFetchType.ID); QueryOptionOffset off = (QueryOptionOffset)query.option(QueryOptionOffset.ID); // if previousPage has detected there is no more data, simply returns an empty list if(sdbCtx.noMoreDataBefore || sdbCtx.noMoreDataAfter){ return; } // manages cursor limitations for IN and != operators with offsets if(!sdbCtx.isActive()){ StringBuffer domainBuf = new StringBuffer(); SelectRequest req = SdbMappingUtils.buildQuery(query, prefix, domainBuf); req.setConsistentRead(isReadConsistent()); checkDomain(domainBuf.toString()); SelectResult res = sdb.select(req); // activates the SdbCtx now that it is really initialised sdbCtx.activate(); postFetch(query, res); // cursor not yet created switch(fetchType.fetchType){ case KEYS_ONLY: if(off.isActive()){ SdbMappingUtils.mapSelectResultToListKeysOnly(res, resList, query.getQueriedClass(), off.offset); }else { SdbMappingUtils.mapSelectResultToListKeysOnly(res, resList, query.getQueriedClass()); } break; case NORMAL: default: if(off.isActive()){ SdbMappingUtils.mapSelectResultToList(res, resList, query.getQueriedClass(), off.offset); }else { SdbMappingUtils.mapSelectResultToList(res, resList, query.getQueriedClass()); } // join management if(!query.getJoins().isEmpty() || !ClassInfo.getClassInfo(query.getQueriedClass()).joinFields.isEmpty()) mapJoins(query, resList); } continueFetchNextToken(query, resList, depth); postMapping(query); } else { // we prepare the query each time StringBuffer domainBuf = new StringBuffer(); SelectRequest req = SdbMappingUtils.buildQuery(query, prefix, domainBuf); req.setConsistentRead(isReadConsistent()); checkDomain(domainBuf.toString()); // we can't use real asynchronous function with cursors // so the page is extracted at once and wrapped into a SienaFuture String token = sdbCtx.currentToken(); if(token!=null){ req.setNextToken(token); } SelectResult res = sdb.select(req); postFetch(query, res); switch(fetchType.fetchType){ case KEYS_ONLY: if(off.isActive()){ SdbMappingUtils.mapSelectResultToListKeysOnly(res, resList, query.getQueriedClass(), off.offset); }else { SdbMappingUtils.mapSelectResultToListKeysOnly(res, resList, query.getQueriedClass()); } break; case NORMAL: default: if(off.isActive()){ SdbMappingUtils.mapSelectResultToList(res, resList, query.getQueriedClass(), off.offset); } else { SdbMappingUtils.mapSelectResultToList(res, resList, query.getQueriedClass()); } // join management if(!query.getJoins().isEmpty() || !ClassInfo.getClassInfo(query.getQueriedClass()).joinFields.isEmpty()) mapJoins(query, resList); } continueFetchNextToken(query, resList, depth); postMapping(query); } } protected <T> Iterable<T> doFetchIterable(Query<T> query, int limit, int offset, boolean recursing) { preFetch(query, limit, offset, recursing); QueryOptionSdbContext sdbCtx = (QueryOptionSdbContext)query.option(QueryOptionSdbContext.ID); // if previousPage has detected there is no more data, simply returns an empty list if(sdbCtx.noMoreDataBefore || sdbCtx.noMoreDataAfter){ return new ArrayList<T>(); } // manages cursor limitations for IN and != operators with offsets if(!sdbCtx.isActive()){ StringBuffer domainBuf = new StringBuffer(); SelectRequest req = SdbMappingUtils.buildQuery(query, prefix, domainBuf); req.setConsistentRead(isReadConsistent()); checkDomain(domainBuf.toString()); SelectResult res = sdb.select(req); // activates the SdbCtx now that it is initialised sdbCtx.activate(); postFetch(query, res); return new SdbSienaIterable<T>(this, res.getItems(), query); } else { // we prepare the query each time StringBuffer domainBuf = new StringBuffer(); SelectRequest req = SdbMappingUtils.buildQuery(query, prefix, domainBuf); req.setConsistentRead(isReadConsistent()); checkDomain(domainBuf.toString()); // we can't use real asynchronous function with cursors // so the page is extracted at once and wrapped into a SienaFuture String token = sdbCtx.currentToken(); if(token!=null){ req.setNextToken(token); } SelectResult res = sdb.select(req); postFetch(query, res); return new SdbSienaIterable<T>(this, res.getItems(), query); } } /* transactions */ public void beginTransaction(int isolationLevel) { } public void beginTransaction() { } public void closeConnection() { } public void commitTransaction() { } public void rollbackTransaction() { } @Override public <T> void release(Query<T> query) { super.release(query); QueryOptionSdbContext sdbCtx = (QueryOptionSdbContext)query.option(QueryOptionSdbContext.ID); if(sdbCtx != null){ sdbCtx.resetAll(); } } @Override public String[] supportedOperators() { return supportedOperators; } @Override public <T> int deleteByKeys(Class<T> clazz, Object... keys) { return deleteByKeys(clazz, Arrays.asList(keys)); } @Override public <T> void nextPage(Query<T> query) { SdbMappingUtils.nextPage(query); } @Override public <T> void previousPage(Query<T> query) { SdbMappingUtils.previousPage(query); } @Override public <T> void paginate(Query<T> query) { QueryOptionSdbContext sdbCtx = (QueryOptionSdbContext)query.option(QueryOptionSdbContext.ID); QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID); if(sdbCtx==null){ sdbCtx = new QueryOptionSdbContext(); query.customize(sdbCtx); } // in stateless, resetting pagination resets everything in the context if(state.isStateless()){ sdbCtx.resetAll(); } QueryOptionOffset off = (QueryOptionOffset)query.option(QueryOptionOffset.ID); off.passivate(); off.offset = 0; } @Override public <T> PersistenceManagerAsync async() { // TODO Auto-generated method stub return null; } @Override public <T> int update(Query<T> query, Map<String, ?> fieldValues) { // TODO Auto-generated method stub return 0; } @Override public <T> int deleteByKeys(Class<T> clazz, Iterable<?> keys) { List<DeletableItem> doList = new ArrayList<DeletableItem>(); int nb = 0; String domain = SdbMappingUtils.getDomainName(clazz, prefix); for(Object key: keys){ doList.add(SdbMappingUtils.createDeletableItemFromKey(clazz, key)); nb++; } try { checkDomain(domain); int len = doList.size() > MAX_ITEMS_PER_CALL ? MAX_ITEMS_PER_CALL: doList.size(); for(int i=0; i < doList.size(); i += len){ int sz = i+len; if(sz > doList.size()){ sz = doList.size(); } sdb.batchDeleteAttributes( new BatchDeleteAttributesRequest( domain, doList.subList(i, sz))); } }catch(AmazonClientException ex){ throw new SienaException(ex); } return nb; } protected <T> T mapJoins(T model) { // join queries Map<Class<?>, Map<String, Object>> classMap = new HashMap<Class<?>, Map<String, Object>>(); // sorts by class and itemName for(Field field: ClassInfo.getClassInfo(model.getClass()).joinFields) { Map<String, Object> strMap = classMap.get(field.getType()); if(strMap == null){ strMap = new HashMap<String, Object>(); classMap.put(field.getType(), strMap); } String itemName = SdbMappingUtils.toString(Util.readField(model, field)); strMap.put(itemName, null); } for(Class<?> clazz: classMap.keySet()){ List<?> objs = this.getByKeys(clazz, classMap.get(clazz).keySet()); Map<String, Object> strMap = classMap.get(clazz); for(Object obj:objs){ String itemName = SdbMappingUtils.getItemName(clazz, obj); strMap.put(itemName, obj); } } for(Field field: ClassInfo.getClassInfo(model.getClass()).joinFields){ String itemName = SdbMappingUtils.toString(Util.readField(model, field)); Util.setField(model, field, classMap.get(field.getType()).get(itemName)); } return model; } protected <T> void mapJoins(Iterable<T> models) { // join queries Map<Class<?>, Map<String, Object>> classMap = new HashMap<Class<?>, Map<String, Object>>(); for(T model: models){ // sorts by class and itemName for(Field field: ClassInfo.getClassInfo(model.getClass()).joinFields) { Map<String, Object> strMap = classMap.get(field.getType()); if(strMap == null){ strMap = new HashMap<String, Object>(); classMap.put(field.getType(), strMap); } String itemName = SdbMappingUtils.toString(Util.readField(model, field)); strMap.put(itemName, null); } } for(Class<?> clazz: classMap.keySet()){ List<?> objs = this.getByKeys(clazz, classMap.get(clazz).keySet()); Map<String, Object> strMap = classMap.get(clazz); for(Object obj:objs){ String itemName = SdbMappingUtils.getItemName(clazz, obj); strMap.put(itemName, obj); } } for(T model: models){ for(Field field: ClassInfo.getClassInfo(model.getClass()).joinFields){ String itemName = SdbMappingUtils.toString(Util.readField(model, field)); Util.setField(model, field, classMap.get(field.getType()).get(itemName)); } } } protected <T> T mapJoins(Query<T> query, T model) { List<QueryJoin> joins = query.getJoins(); // join queries Map<Class<?>, Map<String, Object>> classMap = new HashMap<Class<?>, Map<String, Object>>(); // sorts by class and itemName // joins in query for (QueryJoin join : joins) { Field field = join.field; Map<String, Object> strMap = classMap.get(field.getType()); if(strMap == null){ strMap = new HashMap<String, Object>(); classMap.put(field.getType(), strMap); } String itemName = SdbMappingUtils.toString(Util.readField(model, field)); strMap.put(itemName, null); } // join annotations for(Field field: ClassInfo.getClassInfo(query.getQueriedClass()).joinFields) { Map<String, Object> strMap = classMap.get(field.getType()); if(strMap == null){ strMap = new HashMap<String, Object>(); classMap.put(field.getType(), strMap); } String itemName = SdbMappingUtils.toString(Util.readField(model, field)); strMap.put(itemName, null); } for(Class<?> clazz: classMap.keySet()){ List<?> objs = this.getByKeys(clazz, classMap.get(clazz).keySet()); Map<String, Object> strMap = classMap.get(clazz); for(Object obj:objs){ String itemName = SdbMappingUtils.getItemName(clazz, obj); strMap.put(itemName, obj); } } for(Field field: ClassInfo.getClassInfo(model.getClass()).joinFields){ String itemName = SdbMappingUtils.toString(Util.readField(model, field)); Util.setField(model, field, classMap.get(field.getType()).get(itemName)); } return model; } protected <T> List<T> mapJoins(Query<T> query, List<T> models) { List<QueryJoin> joins = query.getJoins(); // join queries Map<Class<?>, Map<String, Object>> classMap = new HashMap<Class<?>, Map<String, Object>>(); // sorts by class and itemName // joins in query for (final T model : models) { for (QueryJoin join : joins) { Field field = join.field; Map<String, Object> strMap = classMap.get(field.getType()); if(strMap == null){ strMap = new HashMap<String, Object>(); classMap.put(field.getType(), strMap); } String itemName = SdbMappingUtils.toString(Util.readField(model, field)); strMap.put(itemName, null); } // join annotations for(Field field: ClassInfo.getClassInfo(query.getQueriedClass()).joinFields) { Map<String, Object> strMap = classMap.get(field.getType()); if(strMap == null){ strMap = new HashMap<String, Object>(); classMap.put(field.getType(), strMap); } String itemName = SdbMappingUtils.toString(Util.readField(model, field)); strMap.put(itemName, null); } } for(Class<?> clazz: classMap.keySet()){ List<?> objs = this.getByKeys(clazz, classMap.get(clazz).keySet()); Map<String, Object> strMap = classMap.get(clazz); for(Object obj:objs){ String itemName = SdbMappingUtils.getItemName(clazz, obj); strMap.put(itemName, obj); } } for(T model: models){ for (QueryJoin join : joins){ Field field = join.field; String itemName = SdbMappingUtils.toString(Util.readField(model, field)); Util.setField(model, field, classMap.get(field.getType()).get(itemName)); } for(Field field: ClassInfo.getClassInfo(model.getClass()).joinFields){ String itemName = SdbMappingUtils.toString(Util.readField(model, field)); Util.setField(model, field, classMap.get(field.getType()).get(itemName)); } } return models; } }